+
+ );
+
+ assert.strictEqual(
+ selectKit().header().label(),
+ "Normal",
+ "has the correct label"
+ );
+
+ state.topic = buildTopic.call(this, { level: 2 });
+ await settled();
+
+ assert.strictEqual(
+ selectKit().header().label(),
+ "Tracking",
+ "correctly changes the label"
+ );
+ });
+
+ test("the header has a localized title", async function (assert) {
+ I18n.translations.en.js.topic.notifications.tracking_pm.title = `${originalTranslation} PM`;
+ const topic = buildTopic.call(this, {
+ level: 2,
+ archetype: "private_message",
+ });
+
+ await render(
+
+ );
+
+ assert.strictEqual(
+ selectKit().header().label(),
+ `${originalTranslation} PM`,
+ "has the correct label for PMs"
+ );
+ });
+
+ test("notification reason text - user mailing list mode", async function (assert) {
+ this.currentUser.set("user_option.mailing_list_mode", true);
+ const topic = buildTopic.call(this, { level: 2 });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.mailing_list_mode"),
+ "mailing_list_mode enabled for the user shows unique text"
+ );
+ });
+
+ test("notification reason text - bad notification reason", async function (assert) {
+ const state = new TestClass();
+ state.topic = buildTopic.call(this, { level: 2 });
+
+ await render(
+
+ );
+
+ state.topic = buildTopic.call(this, { level: 3, reason: 999 });
+ await settled();
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.3"),
+ "fallback to regular level translation if reason does not exist"
+ );
+ });
+
+ test("notification reason text - user tracking category", async function (assert) {
+ this.currentUser.set("tracked_category_ids", [88]);
+ const topic = buildTopic.call(this, {
+ level: 2,
+ reason: 8,
+ category_id: 88,
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.2_8"),
+ "use 2_8 notification if user is still tracking category"
+ );
+ });
+
+ test("notification reason text - user no longer tracking category", async function (assert) {
+ this.currentUser.set("tracked_category_ids", []);
+ const topic = buildTopic.call(this, {
+ level: 2,
+ reason: 8,
+ category_id: 88,
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.2_8_stale"),
+ "use _stale notification if user is no longer tracking category"
+ );
+ });
+
+ test("notification reason text - user watching category", async function (assert) {
+ this.currentUser.set("watched_category_ids", [88]);
+ const topic = buildTopic.call(this, {
+ level: 3,
+ reason: 6,
+ category_id: 88,
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.3_6"),
+ "use 3_6 notification if user is still watching category"
+ );
+ });
+
+ test("notification reason text - user no longer watching category", async function (assert) {
+ this.currentUser.set("watched_category_ids", []);
+ const topic = buildTopic.call(this, {
+ level: 3,
+ reason: 6,
+ category_id: 88,
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.3_6_stale"),
+ "use _stale notification if user is no longer watching category"
+ );
+ });
+
+ test("notification reason text - user watching tag", async function (assert) {
+ this.currentUser.set("watched_tags", ["test"]);
+ const topic = buildTopic.call(this, {
+ level: 3,
+ reason: 10,
+ tags: ["test"],
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.3_10"),
+ "use 3_10 notification if user is still watching tag"
+ );
+ });
+
+ test("notification reason text - user no longer watching tag", async function (assert) {
+ this.currentUser.set("watched_tags", []);
+ const topic = buildTopic.call(this, {
+ level: 3,
+ reason: 10,
+ tags: ["test"],
+ });
+
+ await render(
+
+ );
+
+ assert
+ .dom(".topic-notifications-button .text")
+ .hasText(
+ I18n.t("topic.notifications.reasons.3_10_stale"),
+ "use _stale notification if user is no longer watching tag"
+ );
+ });
+ }
+);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js
deleted file mode 100644
index b52739bcb2b..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-button-test.js
+++ /dev/null
@@ -1,250 +0,0 @@
-import { getOwner } from "@ember/owner";
-import { render } from "@ember/test-helpers";
-import { hbs } from "ember-cli-htmlbars";
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import selectKit from "discourse/tests/helpers/select-kit-helper";
-import I18n from "discourse-i18n";
-
-function buildTopic(opts) {
- return this.store.createRecord("topic", {
- id: 4563,
- title: "Qunit Test Topic",
- details: {
- notification_level: opts.level,
- notifications_reason_id: opts.reason || null,
- },
- archetype: opts.archetype || "regular",
- category_id: opts.category_id || null,
- tags: opts.tags || [],
- });
-}
-
-const originalTranslation =
- I18n.translations.en.js.topic.notifications.tracking_pm.title;
-
-module(
- "Integration | Component | select-kit/topic-notifications-button",
- function (hooks) {
- setupRenderingTest(hooks);
-
- hooks.beforeEach(function () {
- this.store = getOwner(this).lookup("service:store");
- });
-
- hooks.afterEach(function () {
- I18n.translations.en.js.topic.notifications.tracking_pm.title =
- originalTranslation;
- });
-
- test("the header has a localized title", async function (assert) {
- this.set("topic", buildTopic.call(this, { level: 1 }));
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- selectKit().header().label(),
- "Normal",
- "it has the correct label"
- );
-
- this.set("topic", buildTopic.call(this, { level: 2 }));
-
- assert.strictEqual(
- selectKit().header().label(),
- "Tracking",
- "it correctly changes the label"
- );
- });
-
- test("the header has a localized title", async function (assert) {
- I18n.translations.en.js.topic.notifications.tracking_pm.title = `${originalTranslation} PM`;
- this.set(
- "topic",
- buildTopic.call(this, { level: 2, archetype: "private_message" })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- selectKit().header().label(),
- `${originalTranslation} PM`,
- "it has the correct label for PMs"
- );
- });
-
- test("notification reason text - user mailing list mode", async function (assert) {
- this.currentUser.set("user_option.mailing_list_mode", true);
- this.set("topic", buildTopic.call(this, { level: 2 }));
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.mailing_list_mode"),
- "mailing_list_mode enabled for the user shows unique text"
- );
- });
-
- test("notification reason text - bad notification reason", async function (assert) {
- this.set("topic", buildTopic.call(this, { level: 2 }));
-
- await render(hbs`
-
- `);
-
- this.set("topic", buildTopic.call(this, { level: 3, reason: 999 }));
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.3"),
- "fallback to regular level translation if reason does not exist"
- );
- });
-
- test("notification reason text - user tracking category", async function (assert) {
- this.currentUser.set("tracked_category_ids", [88]);
- this.set(
- "topic",
- buildTopic.call(this, { level: 2, reason: 8, category_id: 88 })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.2_8"),
- "use 2_8 notification if user is still tracking category"
- );
- });
-
- test("notification reason text - user no longer tracking category", async function (assert) {
- this.currentUser.set("tracked_category_ids", []);
- this.set(
- "topic",
- buildTopic.call(this, { level: 2, reason: 8, category_id: 88 })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.2_8_stale"),
- "use _stale notification if user is no longer tracking category"
- );
- });
-
- test("notification reason text - user watching category", async function (assert) {
- this.currentUser.set("watched_category_ids", [88]);
- this.set(
- "topic",
- buildTopic.call(this, { level: 3, reason: 6, category_id: 88 })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.3_6"),
- "use 3_6 notification if user is still watching category"
- );
- });
-
- test("notification reason text - user no longer watching category", async function (assert) {
- this.currentUser.set("watched_category_ids", []);
- this.set(
- "topic",
- buildTopic.call(this, { level: 3, reason: 6, category_id: 88 })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.3_6_stale"),
- "use _stale notification if user is no longer watching category"
- );
- });
-
- test("notification reason text - user watching tag", async function (assert) {
- this.currentUser.set("watched_tags", ["test"]);
- this.set(
- "topic",
- buildTopic.call(this, { level: 3, reason: 10, tags: ["test"] })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.3_10"),
- "use 3_10 notification if user is still watching tag"
- );
- });
-
- test("notification reason text - user no longer watching tag", async function (assert) {
- this.currentUser.set("watched_tags", []);
- this.set(
- "topic",
- buildTopic.call(this, { level: 3, reason: 10, tags: ["test"] })
- );
-
- await render(hbs`
-
- `);
-
- assert.strictEqual(
- query(".topic-notifications-button .text").innerText,
- I18n.t("topic.notifications.reasons.3_10_stale"),
- "use _stale notification if user is no longer watching tag"
- );
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.gjs
similarity index 67%
rename from app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js
rename to app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.gjs
index dd24f162b8a..80edada0611 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/select-kit/topic-notifications-options-test.gjs
@@ -1,10 +1,10 @@
import { getOwner } from "@ember/owner";
import { render } from "@ember/test-helpers";
-import { hbs } from "ember-cli-htmlbars";
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import selectKit from "discourse/tests/helpers/select-kit-helper";
import I18n from "discourse-i18n";
+import TopicNotificationsOptions from "select-kit/components/topic-notifications-options";
function extractDescriptions(rows) {
return [...rows].map((el) => el.querySelector(".desc").textContent.trim());
@@ -23,24 +23,21 @@ module(
test("regular topic notification level descriptions", async function (assert) {
const store = getOwner(this).lookup("service:store");
- this.set(
- "topic",
- store.createRecord("topic", {
- id: 4563,
- title: "Qunit Test Topic",
- archetype: "regular",
- details: {
- notification_level: 1,
- },
- })
- );
+ const topic = store.createRecord("topic", {
+ id: 4563,
+ title: "Qunit Test Topic",
+ archetype: "regular",
+ details: {
+ notification_level: 1,
+ },
+ });
- await render(hbs`
+ await render(
- `);
+ );
await selectKit().expand();
@@ -50,38 +47,35 @@ module(
assert.strictEqual(
uiTexts.length,
descriptions.length,
- "it has the correct copy"
+ "has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
- "it has the correct copy"
+ "has the correct copy"
);
});
});
test("PM topic notification level descriptions", async function (assert) {
const store = getOwner(this).lookup("service:store");
- this.set(
- "topic",
- store.createRecord("topic", {
- id: 4563,
- title: "Qunit Test Topic",
- archetype: "private_message",
- details: {
- notification_level: 1,
- },
- })
- );
+ const topic = store.createRecord("topic", {
+ id: 4563,
+ title: "Qunit Test Topic",
+ archetype: "private_message",
+ details: {
+ notification_level: 1,
+ },
+ });
- await render(hbs`
+ await render(
- `);
+ );
await selectKit().expand();
@@ -91,14 +85,14 @@ module(
assert.strictEqual(
uiTexts.length,
descriptions.length,
- "it has the correct copy"
+ "has the correct copy"
);
uiTexts.forEach((text, index) => {
assert.strictEqual(
text.trim(),
descriptions[index].trim(),
- "it has the correct copy"
+ "has the correct copy"
);
});
});
diff --git a/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.gjs b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.gjs
new file mode 100644
index 00000000000..80d018ae8c1
--- /dev/null
+++ b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.gjs
@@ -0,0 +1,147 @@
+import Component from "@glimmer/component";
+import { tracked } from "@glimmer/tracking";
+import { hash } from "@ember/helper";
+import { action } from "@ember/object";
+import { service } from "@ember/service";
+import { htmlSafe } from "@ember/template";
+import { isEmpty } from "@ember/utils";
+import { NotificationLevels } from "discourse/lib/notification-levels";
+import i18n from "discourse-common/helpers/i18n";
+import getURL from "discourse-common/lib/get-url";
+import I18n from "discourse-i18n";
+import TopicNotificationsOptions from "select-kit/components/topic-notifications-options";
+
+export default class TopicNotificationsButton extends Component {
+ @service currentUser;
+
+ @tracked isLoading = false;
+
+ get notificationLevel() {
+ return this.args.topic.get("details.notification_level");
+ }
+
+ get appendReason() {
+ return this.args.appendReason ?? true;
+ }
+
+ get showFullTitle() {
+ return this.args.showFullTitle ?? true;
+ }
+
+ get showCaret() {
+ return this.args.showCaret ?? true;
+ }
+
+ get reasonText() {
+ const topic = this.args.topic;
+ const level = topic.get("details.notification_level") ?? 1;
+ const reason = topic.get("details.notifications_reason_id");
+ let localeString = `topic.notifications.reasons.${level}`;
+
+ if (typeof reason === "number") {
+ let localeStringWithReason = `${localeString}_${reason}`;
+
+ if (this._reasonStale(level, reason)) {
+ localeStringWithReason += "_stale";
+ }
+
+ // some sane protection for missing translations of edge cases
+ if (I18n.lookup(localeStringWithReason, { locale: "en" })) {
+ localeString = localeStringWithReason;
+ }
+ }
+
+ if (
+ this.currentUser?.user_option.mailing_list_mode &&
+ level > NotificationLevels.MUTED
+ ) {
+ return I18n.t("topic.notifications.reasons.mailing_list_mode");
+ } else {
+ return I18n.t(localeString, {
+ username: this.currentUser?.username_lower,
+ basePath: getURL(""),
+ });
+ }
+ }
+
+ // The user may have changed their category or tag tracking settings
+ // since this topic was tracked/watched based on those settings in the
+ // past. In that case we need to alter the reason message we show them
+ // otherwise it is very confusing for the end user to be told they are
+ // tracking a topic because of a category, when they are no longer tracking
+ // that category.
+ _reasonStale(level, reason) {
+ if (!this.currentUser) {
+ return;
+ }
+
+ const watchedCategoryIds = this.currentUser.watched_category_ids || [];
+ const trackedCategoryIds = this.currentUser.tracked_category_ids || [];
+ const watchedTags = this.currentUser.watched_tags || [];
+
+ if (this.args.topic.category_id) {
+ if (level === 2 && reason === 8) {
+ // 2_8 tracking category
+ return !trackedCategoryIds.includes(this.args.topic.category_id);
+ } else if (level === 3 && reason === 6) {
+ // 3_6 watching category
+ return !watchedCategoryIds.includes(this.args.topic.category_id);
+ }
+ } else if (!isEmpty(this.args.topic.tags)) {
+ if (level === 3 && reason === 10) {
+ // 3_10 watching tag
+ return !this.args.topic.tags.some((tag) => watchedTags.includes(tag));
+ }
+ }
+
+ return false;
+ }
+
+ @action
+ async changeTopicNotificationLevel(levelId) {
+ if (levelId === this.notificationLevel) {
+ return;
+ }
+
+ this.isLoading = true;
+
+ try {
+ await this.args.topic.details.updateNotifications(levelId);
+ } finally {
+ this.isLoading = false;
+ }
+ }
+
+
+
+
+}
diff --git a/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.hbs b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.hbs
deleted file mode 100644
index eb980433ca3..00000000000
--- a/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.hbs
+++ /dev/null
@@ -1,28 +0,0 @@
-{{#if this.appendReason}}
-
-
- {{html-safe this.notificationReasonText}}
-
-{{else}}
-
-{{/if}}
\ No newline at end of file
diff --git a/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js b/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js
deleted file mode 100644
index 8fc2121ae77..00000000000
--- a/app/assets/javascripts/select-kit/addon/components/topic-notifications-button.js
+++ /dev/null
@@ -1,118 +0,0 @@
-import Component from "@ember/component";
-import { action, computed } from "@ember/object";
-import { isEmpty } from "@ember/utils";
-import { classNameBindings, classNames } from "@ember-decorators/component";
-import { NotificationLevels } from "discourse/lib/notification-levels";
-import getURL from "discourse-common/lib/get-url";
-import discourseComputed from "discourse-common/utils/decorators";
-import I18n from "discourse-i18n";
-
-@classNames("topic-notifications-button")
-@classNameBindings("isLoading")
-export default class TopicNotificationsButton extends Component {
- appendReason = true;
- showFullTitle = true;
- notificationLevel = null;
- topic = null;
- showCaret = true;
- isLoading = false;
-
- @computed("isLoading")
- get icon() {
- return this.isLoading ? "spinner" : null;
- }
-
- @action
- changeTopicNotificationLevel(levelId) {
- if (levelId !== this.notificationLevel) {
- this.set("isLoading", true);
- this.topic.details
- .updateNotifications(levelId)
- .finally(() => this.set("isLoading", false));
- }
- }
-
- @discourseComputed(
- "topic",
- "topic.details.{notification_level,notifications_reason_id}"
- )
- notificationReasonText(topic, topicDetails) {
- let level = topicDetails.notification_level;
- let reason = topicDetails.notifications_reason_id;
-
- if (typeof level !== "number") {
- level = 1;
- }
-
- let localeString = `topic.notifications.reasons.${level}`;
- if (typeof reason === "number") {
- let localeStringWithReason = localeString + "_" + reason;
-
- if (
- this._notificationReasonStale(level, reason, topic, this.currentUser)
- ) {
- localeStringWithReason += "_stale";
- }
-
- // some sane protection for missing translations of edge cases
- if (I18n.lookup(localeStringWithReason, { locale: "en" })) {
- localeString = localeStringWithReason;
- }
- }
-
- if (
- this.currentUser &&
- this.currentUser.user_option.mailing_list_mode &&
- level > NotificationLevels.MUTED
- ) {
- return I18n.t("topic.notifications.reasons.mailing_list_mode");
- } else {
- return I18n.t(localeString, {
- username: this.currentUser && this.currentUser.username_lower,
- basePath: getURL(""),
- });
- }
- }
-
- // The user may have changed their category or tag tracking settings
- // since this topic was tracked/watched based on those settings in the
- // past. In that case we need to alter the reason message we show them
- // otherwise it is very confusing for the end user to be told they are
- // tracking a topic because of a category, when they are no longer tracking
- // that category.
- _notificationReasonStale(level, reason, topic, currentUser) {
- if (!currentUser) {
- return;
- }
-
- let categoryId = topic.category_id;
- let tags = topic.tags;
- let watchedCategoryIds = currentUser.watched_category_ids || [];
- let trackedCategoryIds = currentUser.tracked_category_ids || [];
- let watchedTags = currentUser.watched_tags || [];
-
- // 2_8 tracking category
- if (categoryId) {
- if (level === 2 && reason === 8) {
- if (!trackedCategoryIds.includes(categoryId)) {
- return true;
- }
-
- // 3_6 watching category
- } else if (level === 3 && reason === 6) {
- if (!watchedCategoryIds.includes(categoryId)) {
- return true;
- }
- }
- } else if (!isEmpty(tags)) {
- // 3_10 watching tag
- if (level === 3 && reason === 10) {
- if (!tags.some((tag) => watchedTags.includes(tag))) {
- return true;
- }
- }
- }
-
- return false;
- }
-}