diff --git a/app/assets/javascripts/discourse/app/components/user-menu/group-mentioned-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/group-mentioned-notification-item.js
deleted file mode 100644
index 217709f9d18..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-menu/group-mentioned-notification-item.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
-
-export default class UserMenuGroupMentionedNotificationItem extends UserMenuNotificationItem {
- get label() {
- return `${this.username} @${this.notification.data.group_name}`;
- }
-
- get labelWrapperClasses() {
- return "mention-group notify";
- }
-}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/group-message-summary-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/group-message-summary-notification-item.js
deleted file mode 100644
index 2f4eda9f5f5..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-menu/group-message-summary-notification-item.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
-import I18n from "I18n";
-
-export default class UserMenuGroupMessageSummaryNotificationItem extends UserMenuNotificationItem {
- get inboxCount() {
- return this.notification.data.inbox_count;
- }
-
- get label() {
- return I18n.t("notifications.group_message_summary", {
- count: this.inboxCount,
- group_name: this.notification.data.group_name,
- });
- }
-
- get wrapLabel() {
- return false;
- }
-
- get description() {
- return null;
- }
-}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/items-list.hbs b/app/assets/javascripts/discourse/app/components/user-menu/items-list.hbs
index 4961f29bf5b..fab0afadc89 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/items-list.hbs
+++ b/app/assets/javascripts/discourse/app/components/user-menu/items-list.hbs
@@ -5,7 +5,7 @@
{{else if this.items.length}}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/items-list.js b/app/assets/javascripts/discourse/app/components/user-menu/items-list.js
index 31585618afb..47a46b7f72f 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/items-list.js
+++ b/app/assets/javascripts/discourse/app/components/user-menu/items-list.js
@@ -28,6 +28,12 @@ export default class UserMenuItemsList extends GlimmerComponent {
return "user-menu/items-list-empty-state";
}
+ get itemComponent() {
+ throw new Error(
+ `the itemComponent property must be implemented in ${this.constructor.name}`
+ );
+ }
+
fetchItems() {
throw new Error(
`the fetchItems method must be implemented in ${this.constructor.name}`
@@ -51,17 +57,6 @@ export default class UserMenuItemsList extends GlimmerComponent {
}
this.fetchItems()
.then((items) => {
- const valid = items.every((item) => {
- if (!item.userMenuComponent) {
- // eslint-disable-next-line no-console
- console.error("userMenuComponent property is blank on", item);
- return false;
- }
- return true;
- });
- if (!valid) {
- throw new Error("userMenuComponent must be present on all items");
- }
this._setCachedItems(items);
this.items = items;
})
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/liked-consolidated-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/liked-consolidated-notification-item.js
deleted file mode 100644
index 1591c4d6476..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-menu/liked-consolidated-notification-item.js
+++ /dev/null
@@ -1,21 +0,0 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
-import { userPath } from "discourse/lib/url";
-import I18n from "I18n";
-
-export default class UserMenuLikedConsolidatedNotificationItem extends UserMenuNotificationItem {
- get linkHref() {
- return userPath(
- `${
- this.notification.username || this.currentUser.username
- }/notifications/likes-received?acting_username=${
- this.notification.data.username
- }`
- );
- }
-
- get description() {
- return I18n.t("notifications.liked_consolidated_description", {
- count: this.notification.data.count,
- });
- }
-}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/moved-post-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/moved-post-notification-item.js
deleted file mode 100644
index e4394918fae..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-menu/moved-post-notification-item.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
-import I18n from "I18n";
-
-export default class UserMenuMovedPostNotificationItem extends UserMenuNotificationItem {
- get label() {
- return I18n.t("notifications.user_moved_post", { username: this.username });
- }
-}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.hbs b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.hbs
index fcb6c90b1ec..b9e155ad1c6 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.hbs
+++ b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.hbs
@@ -7,17 +7,13 @@
{{d-icon this.icon}}
{{#if this.label}}
- {{#if this.wrapLabel}}
-
- {{this.label}}
-
- {{else}}
-
{{this.label}}
- {{/if}}
+
+ {{this.label}}
+
{{/if}}
{{#if this.description}}
{{this.description}}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js
index 007cc63830a..084c84962ce 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js
+++ b/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js
@@ -1,98 +1,73 @@
import GlimmerComponent from "discourse/components/glimmer";
-import { formatUsername, postUrl } from "discourse/lib/utilities";
-import { userPath } from "discourse/lib/url";
import { setTransientHeader } from "discourse/lib/ajax";
import { action } from "@ember/object";
-import { emojiUnescape } from "discourse/lib/text";
-import { htmlSafe } from "@ember/template";
+import { getRenderDirector } from "discourse/lib/notification-item";
import getURL from "discourse-common/lib/get-url";
import cookie from "discourse/lib/cookie";
-import I18n from "I18n";
export default class UserMenuNotificationItem extends GlimmerComponent {
+ constructor() {
+ super(...arguments);
+ this.renderDirector = getRenderDirector(
+ this.#notificationName,
+ this.notification,
+ this.currentUser,
+ this.siteSettings,
+ this.site
+ );
+ }
+
get className() {
const classes = [];
if (this.notification.read) {
classes.push("read");
}
- if (this.notificationName) {
- classes.push(this.notificationName.replace(/_/g, "-"));
+ if (this.#notificationName) {
+ classes.push(this.#notificationName.replace(/_/g, "-"));
}
if (this.notification.is_warning) {
classes.push("is-warning");
}
+ const extras = this.renderDirector.classNames;
+ if (extras?.length) {
+ classes.push(...extras);
+ }
return classes.join(" ");
}
get linkHref() {
- if (this.topicId) {
- return postUrl(
- this.notification.slug,
- this.topicId,
- this.notification.post_number
- );
- }
- if (this.notification.data.group_id) {
- return userPath(
- `${this.notification.data.username}/messages/${this.notification.data.group_name}`
- );
- }
+ return this.renderDirector.linkHref;
}
get linkTitle() {
- if (this.notificationName) {
- return I18n.t(`notifications.titles.${this.notificationName}`);
- } else {
- return "";
- }
+ return this.renderDirector.linkTitle;
}
get icon() {
- return `notification.${this.notificationName}`;
+ return this.renderDirector.icon;
}
get label() {
- return this.username;
+ return this.renderDirector.label;
}
- get wrapLabel() {
- return true;
- }
-
- get labelWrapperClasses() {}
-
- get username() {
- return formatUsername(this.notification.data.display_username);
+ get labelWrapperClasses() {
+ return this.renderDirector.labelWrapperClasses?.join(" ") || "";
}
get description() {
- const description =
- emojiUnescape(this.notification.fancy_title) ||
- this.notification.data.topic_title;
-
- if (this.descriptionHtmlSafe) {
- return htmlSafe(description);
- } else {
- return description;
- }
+ return this.renderDirector.description;
}
- get descriptionElementClasses() {}
-
- get descriptionHtmlSafe() {
- return !!this.notification.fancy_title;
+ get descriptionWrapperClasses() {
+ return this.renderDirector.descriptionWrapperClasses?.join(" ") || "";
}
- // the following props are helper props -- they're never referenced directly in the hbs template
get notification() {
return this.args.item;
}
- get topicId() {
- return this.notification.topic_id;
- }
-
- get notificationName() {
+ get #notificationName() {
return this.site.notificationLookup[this.notification.notification_type];
}
@@ -103,5 +78,6 @@ export default class UserMenuNotificationItem extends GlimmerComponent {
setTransientHeader("Discourse-Clear-Notifications", this.notification.id);
cookie("cn", this.notification.id, { path: getURL("/") });
}
+ this.renderDirector.onClick();
}
}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js
index 4e5c59670dd..79110e5bcd2 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js
+++ b/app/assets/javascripts/discourse/app/components/user-menu/notifications-list.js
@@ -43,6 +43,10 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
}
}
+ get itemComponent() {
+ return "user-menu/notification-item";
+ }
+
fetchItems() {
const params = {
limit: 30,
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/default-reviewable-item.hbs b/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.hbs
similarity index 100%
rename from app/assets/javascripts/discourse/app/components/user-menu/default-reviewable-item.hbs
rename to app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.hbs
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js b/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js
new file mode 100644
index 00000000000..b08d973fbd0
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js
@@ -0,0 +1,28 @@
+import GlimmerComponent from "discourse/components/glimmer";
+import { getRenderDirector } from "discourse/lib/reviewable-item";
+
+export default class UserMenuReviewableItem extends GlimmerComponent {
+ constructor() {
+ super(...arguments);
+ this.reviewable = this.args.item;
+ this.renderDirector = getRenderDirector(
+ this.reviewable.type,
+ this.reviewable,
+ this.currentUser,
+ this.siteSettings,
+ this.site
+ );
+ }
+
+ get actor() {
+ return this.renderDirector.actor;
+ }
+
+ get description() {
+ return this.renderDirector.description;
+ }
+
+ get icon() {
+ return this.renderDirector.icon;
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewables-list.js b/app/assets/javascripts/discourse/app/components/user-menu/reviewables-list.js
index 61b5c87e7d2..2b1be914328 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/reviewables-list.js
+++ b/app/assets/javascripts/discourse/app/components/user-menu/reviewables-list.js
@@ -17,6 +17,10 @@ export default class UserMenuReviewablesList extends UserMenuItemsList {
return "pending-reviewables";
}
+ get itemComponent() {
+ return "user-menu/reviewable-item";
+ }
+
fetchItems() {
return ajax("/review/user-menu-list").then((data) => {
return data.reviewables.map((item) => {
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/watching-first-post-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/watching-first-post-notification-item.js
deleted file mode 100644
index 7d917d2e74d..00000000000
--- a/app/assets/javascripts/discourse/app/components/user-menu/watching-first-post-notification-item.js
+++ /dev/null
@@ -1,8 +0,0 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
-import I18n from "I18n";
-
-export default class UserMenuWatchingFirstPostNotificationItem extends UserMenuNotificationItem {
- get label() {
- return I18n.t("notifications.watching_first_post_label");
- }
-}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-item.js
new file mode 100644
index 00000000000..5ffeb4af9f2
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-item.js
@@ -0,0 +1,51 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+
+import BookmarkReminder from "discourse/lib/notification-items/bookmark-reminder";
+import Custom from "discourse/lib/notification-items/custom";
+import GrantedBadge from "discourse/lib/notification-items/granted-badge";
+import GroupMentioned from "discourse/lib/notification-items/group-mentioned";
+import GroupMessageSummary from "discourse/lib/notification-items/group-message-summary";
+import InviteeAccepted from "discourse/lib/notification-items/invitee-accepted";
+import LikedConsolidated from "discourse/lib/notification-items/liked-consolidated";
+import Liked from "discourse/lib/notification-items/liked";
+import MembershipRequestAccepted from "discourse/lib/notification-items/membership-request-accepted";
+import MembershipRequestConsolidated from "discourse/lib/notification-items/membership-request-consolidated";
+import MovedPost from "discourse/lib/notification-items/moved-post";
+import WatchingFirstPost from "discourse/lib/notification-items/watching-first-post";
+
+const CLASS_FOR_TYPE = {
+ bookmark_reminder: BookmarkReminder,
+ custom: Custom,
+ granted_badge: GrantedBadge,
+ group_mentioned: GroupMentioned,
+ group_message_summary: GroupMessageSummary,
+ invitee_accepted: InviteeAccepted,
+ liked: Liked,
+ liked_consolidated: LikedConsolidated,
+ membership_request_accepted: MembershipRequestAccepted,
+ membership_request_consolidated: MembershipRequestConsolidated,
+ moved_post: MovedPost,
+ watching_first_post: WatchingFirstPost,
+};
+
+let _customClassForType = {};
+
+export function registerNotificationTypeRenderer(notificationType, func) {
+ _customClassForType[notificationType] = func(NotificationItemBase);
+}
+
+export function resetRenderDirectorForNotifictaionTypes() {
+ _customClassForType = {};
+}
+
+export function getRenderDirector(
+ type,
+ notification,
+ currentUser,
+ siteSettings,
+ site
+) {
+ const klass =
+ _customClassForType[type] || CLASS_FOR_TYPE[type] || NotificationItemBase;
+ return new klass({ notification, currentUser, siteSettings, site });
+}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/base.js b/app/assets/javascripts/discourse/app/lib/notification-items/base.js
new file mode 100644
index 00000000000..455db134b0b
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/base.js
@@ -0,0 +1,109 @@
+import { formatUsername, postUrl } from "discourse/lib/utilities";
+import { userPath } from "discourse/lib/url";
+import { emojiUnescape } from "discourse/lib/text";
+import { htmlSafe } from "@ember/template";
+import I18n from "I18n";
+
+export default class NotificationItemBase {
+ constructor({ notification, currentUser, siteSettings, site }) {
+ this.notification = notification;
+ this.currentUser = currentUser;
+ this.siteSettings = siteSettings;
+ this.site = site;
+ }
+
+ /**
+ * @returns {string[]} An array of addtional classes that should be added to the element of the notification item.
+ */
+ get classNames() {
+ return [];
+ }
+
+ /**
+ * @returns {string} A href/path that the notification item should link to.
+ */
+ get linkHref() {
+ if (this.topicId) {
+ return postUrl(
+ this.notification.slug,
+ this.topicId,
+ this.notification.post_number
+ );
+ }
+ if (this.notification.data.group_id) {
+ return userPath(
+ `${this.notification.data.username}/messages/${this.notification.data.group_name}`
+ );
+ }
+ }
+
+ /**
+ * @returns {string} A title for the notification item. It shows up when the user hovers over the notification item.
+ */
+ get linkTitle() {
+ if (this.notificationName) {
+ return I18n.t(`notifications.titles.${this.notificationName}`);
+ } else {
+ // notifications with unknown types, e.g. notifications that come from a
+ // plugin that's no longer installed
+ return "";
+ }
+ }
+
+ /**
+ * @returns {string} An icon for the notification item.
+ */
+ get icon() {
+ return `notification.${this.notificationName}`;
+ }
+
+ /**
+ * @returns {string} The label is the first part of the text content displayed in the notification. For example, in a like notification, the username of the user who liked the post is the label. If a falsey value is returned, the label is omitted.
+ */
+ get label() {
+ return this.username;
+ }
+
+ /**
+ * @returns {string} The description is the second part of the text content displayed in the notification. For example, in a like notification, the topic title is the description. If a falsey value is returned, the description is omitted.
+ */
+ get description() {
+ const description = emojiUnescape(this.notification.fancy_title);
+ if (description) {
+ return htmlSafe(description);
+ } else {
+ return this.notification.data.topic_title;
+ }
+ }
+
+ /**
+ * Function that is called when the notification item is clicked.
+ */
+ onClick() {}
+
+ /**
+ * @returns {string[]} Include additional classes to the label's wrapper .
+ */
+ get labelWrapperClasses() {
+ return [];
+ }
+
+ /**
+ * @returns {string[]} Include additional classes to the description's wrapper .
+ */
+ get descriptionWrapperClasses() {
+ return [];
+ }
+
+ get topicId() {
+ return this.notification.topic_id;
+ }
+
+ get username() {
+ return formatUsername(this.notification.data.display_username);
+ }
+
+ get notificationName() {
+ return this.site.notificationLookup[this.notification.notification_type];
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-reminder-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js
similarity index 65%
rename from app/assets/javascripts/discourse/app/components/user-menu/bookmark-reminder-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js
index 519ef8ec0d0..d465f4a71b9 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-reminder-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js
@@ -1,7 +1,7 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import I18n from "I18n";
-export default class UserMenuBookmarkReminderNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkTitle() {
if (this.notification.data.bookmark_name) {
return I18n.t("notifications.titles.bookmark_reminder_with_name", {
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/custom-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/custom.js
similarity index 59%
rename from app/assets/javascripts/discourse/app/components/user-menu/custom-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/custom.js
index 269664f2701..09006e37a87 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/custom-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/custom.js
@@ -1,7 +1,7 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import I18n from "I18n";
-export default class UserMenuCustomNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkTitle() {
if (this.notification.data.title) {
return I18n.t(this.notification.data.title);
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/granted-badge-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js
similarity index 77%
rename from app/assets/javascripts/discourse/app/components/user-menu/granted-badge-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js
index 5da5a681340..13e6b2ff31a 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/granted-badge-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js
@@ -1,8 +1,8 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import getURL from "discourse-common/lib/get-url";
import I18n from "I18n";
-export default class UserMenuGrantedBadgeNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkHref() {
const badgeId = this.notification.data.badge_id;
if (badgeId) {
@@ -20,17 +20,13 @@ export default class UserMenuGrantedBadgeNotificationItem extends UserMenuNotifi
}
}
- get label() {
+ get description() {
return I18n.t("notifications.granted_badge", {
description: this.notification.data.badge_name,
});
}
- get wrapLabel() {
- return false;
- }
-
- get description() {
+ get label() {
return null;
}
}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js b/app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js
new file mode 100644
index 00000000000..0333d6e44d8
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js
@@ -0,0 +1,11 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+
+export default class extends NotificationItemBase {
+ get label() {
+ return `${this.username} @${this.notification.data.group_name}`;
+ }
+
+ get labelWrapperClasses() {
+ return ["mention-group", "notify"];
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js b/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js
new file mode 100644
index 00000000000..2c02a414fc7
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js
@@ -0,0 +1,15 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+import I18n from "I18n";
+
+export default class extends NotificationItemBase {
+ get description() {
+ return I18n.t("notifications.group_message_summary", {
+ count: this.notification.data.inbox_count,
+ group_name: this.notification.data.group_name,
+ });
+ }
+
+ get label() {
+ return null;
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/invitee-accepted-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js
similarity index 57%
rename from app/assets/javascripts/discourse/app/components/user-menu/invitee-accepted-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js
index e4635f2e43e..4761a6f4321 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/invitee-accepted-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js
@@ -1,8 +1,8 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import { userPath } from "discourse/lib/url";
import I18n from "I18n";
-export default class UserMenuInviteeAcceptedNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkHref() {
return userPath(this.notification.data.display_username);
}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/liked-consolidated.js b/app/assets/javascripts/discourse/app/lib/notification-items/liked-consolidated.js
new file mode 100644
index 00000000000..639fc1a1ab2
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/liked-consolidated.js
@@ -0,0 +1,18 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+import { userPath } from "discourse/lib/url";
+import I18n from "I18n";
+
+export default class extends NotificationItemBase {
+ get linkHref() {
+ // TODO(osama): serialize username with notifications
+ return userPath(
+ `${this.currentUser.username}/notifications/likes-received?acting_username=${this.notification.data.username}`
+ );
+ }
+
+ get description() {
+ return I18n.t("notifications.liked_consolidated_description", {
+ count: this.notification.data.count,
+ });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/liked-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/liked.js
similarity index 67%
rename from app/assets/javascripts/discourse/app/components/user-menu/liked-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/liked.js
index e4f8b02ce67..6cae82971b6 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/liked-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/liked.js
@@ -1,26 +1,18 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import { formatUsername } from "discourse/lib/utilities";
import I18n from "I18n";
-export default class UserMenuLikedNotificationItem extends UserMenuNotificationItem {
- get count() {
- return this.notification.data.count;
- }
-
- get username2() {
- return formatUsername(this.notification.data.username2);
- }
-
+export default class extends NotificationItemBase {
get label() {
if (this.count === 2) {
return I18n.t("notifications.liked_by_2_users", {
username: this.username,
- username2: this.username2,
+ username2: this.#username2,
});
} else if (this.count > 2) {
return I18n.t("notifications.liked_by_multiple_users", {
username: this.username,
- username2: this.username2,
+ username2: this.#username2,
count: this.count - 2,
});
} else {
@@ -30,9 +22,17 @@ export default class UserMenuLikedNotificationItem extends UserMenuNotificationI
get labelWrapperClasses() {
if (this.count === 2) {
- return "double-user";
+ return ["double-user"];
} else if (this.count > 2) {
- return "multi-user";
+ return ["multi-user"];
}
}
+
+ get count() {
+ return this.notification.data.count;
+ }
+
+ get #username2() {
+ return formatUsername(this.notification.data.username2);
+ }
}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/membership-request-accepted-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js
similarity index 59%
rename from app/assets/javascripts/discourse/app/components/user-menu/membership-request-accepted-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js
index 850dc4c084c..d32ec41f271 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/membership-request-accepted-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js
@@ -1,23 +1,19 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import { groupPath } from "discourse/lib/url";
import I18n from "I18n";
-export default class UserMenuMembershipRequestAcceptedNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkHref() {
return groupPath(this.notification.data.group_name);
}
- get label() {
+ get description() {
return I18n.t("notifications.membership_request_accepted", {
group_name: this.notification.data.group_name,
});
}
- get wrapLabel() {
- return false;
- }
-
- get description() {
+ get label() {
return null;
}
}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/membership-request-consolidated-notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js
similarity index 64%
rename from app/assets/javascripts/discourse/app/components/user-menu/membership-request-consolidated-notification-item.js
rename to app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js
index 0fe0faa7282..0f52b5ea031 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/membership-request-consolidated-notification-item.js
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js
@@ -1,26 +1,22 @@
-import UserMenuNotificationItem from "discourse/components/user-menu/notification-item";
+import NotificationItemBase from "discourse/lib/notification-items/base";
import { userPath } from "discourse/lib/url";
import I18n from "I18n";
-export default class UserMenuMembershipRequestConsolidatedNotificationItem extends UserMenuNotificationItem {
+export default class extends NotificationItemBase {
get linkHref() {
return userPath(
`${this.notification.username || this.currentUser.username}/messages`
);
}
- get label() {
+ get description() {
return I18n.t("notifications.membership_request_consolidated", {
group_name: this.notification.data.group_name,
count: this.notification.data.count,
});
}
- get wrapLabel() {
- return false;
- }
-
- get description() {
+ get label() {
return null;
}
}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js b/app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js
new file mode 100644
index 00000000000..58ed1bc37ef
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js
@@ -0,0 +1,8 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+import I18n from "I18n";
+
+export default class extends NotificationItemBase {
+ get label() {
+ return I18n.t("notifications.user_moved_post", { username: this.username });
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/watching-first-post.js b/app/assets/javascripts/discourse/app/lib/notification-items/watching-first-post.js
new file mode 100644
index 00000000000..abcecc3b7ca
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/notification-items/watching-first-post.js
@@ -0,0 +1,8 @@
+import NotificationItemBase from "discourse/lib/notification-items/base";
+import I18n from "I18n";
+
+export default class extends NotificationItemBase {
+ get label() {
+ return I18n.t("notifications.watching_first_post_label");
+ }
+}
diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js
index 84eb5dbe7fd..2f1489866b1 100644
--- a/app/assets/javascripts/discourse/app/lib/plugin-api.js
+++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js
@@ -98,6 +98,7 @@ import { consolePrefix } from "discourse/lib/source-identifier";
import { addSectionLink as addCustomCommunitySectionLink } from "discourse/lib/sidebar/custom-community-section-links";
import { addSidebarSection } from "discourse/lib/sidebar/custom-sections";
import DiscourseURL from "discourse/lib/url";
+import { registerNotificationTypeRenderer } from "discourse/lib/notification-item";
// If you add any methods to the API ensure you bump up the version number
// based on Semantic Versioning 2.0.0. Please update the changelog at
@@ -1851,6 +1852,36 @@ class PluginApi {
addSidebarSection(func) {
addSidebarSection(func);
}
+
+ /**
+ * EXPERIMENTAL. Do not use.
+ * Register a custom renderer for a notification type or override the
+ * renderer of an existing type. See lib/notification-items/base.js for
+ * documentation and the default renderer.
+ *
+ * ```
+ * api.registerNotificationTypeRenderer("your_notification_type", (NotificationItemBase) => {
+ * return class extends NotificationItemBase {
+ * get label() {
+ * return "some label";
+ * }
+ *
+ * get description() {
+ * return "fancy description";
+ * }
+ * };
+ * });
+ * ```
+ * @callback renderDirectorRegistererCallback
+ * @param {NotificationItemBase} The base class from which the returned class should inherit.
+ * @returns {NotificationItemBase} A class that inherits from NotificationItemBase.
+ *
+ * @param {string} notificationType - ID of the notification type (i.e. the key value of your notification type in the `Notification.types` enum on the server side).
+ * @param {renderDirectorRegistererCallback} func - Callback function that returns a subclass from the class it receives as its argument.
+ */
+ registerNotificationTypeRenderer(notificationType, func) {
+ registerNotificationTypeRenderer(notificationType, func);
+ }
}
// from http://stackoverflow.com/questions/6832596/how-to-compare-software-version-number-using-js-only-number
diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-item.js
new file mode 100644
index 00000000000..7ef29025043
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/lib/reviewable-item.js
@@ -0,0 +1,22 @@
+import ReviewableItemBase from "discourse/lib/reviewable-items/base";
+
+import FlaggedPost from "discourse/lib/reviewable-items/flagged-post";
+import QueuedPost from "discourse/lib/reviewable-items/queued-post";
+import ReviewableUser from "discourse/lib/reviewable-items/user";
+
+const CLASS_FOR_TYPE = {
+ ReviewableFlaggedPost: FlaggedPost,
+ ReviewableQueuedPost: QueuedPost,
+ ReviewableUser,
+};
+
+export function getRenderDirector(
+ type,
+ reviewable,
+ currentUser,
+ siteSettings,
+ site
+) {
+ const klass = CLASS_FOR_TYPE[type] || ReviewableItemBase;
+ return new klass({ reviewable, currentUser, siteSettings, site });
+}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/default-reviewable-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-items/base.js
similarity index 62%
rename from app/assets/javascripts/discourse/app/components/user-menu/default-reviewable-item.js
rename to app/assets/javascripts/discourse/app/lib/reviewable-items/base.js
index 7cf8154dc5c..b8a71c36212 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/default-reviewable-item.js
+++ b/app/assets/javascripts/discourse/app/lib/reviewable-items/base.js
@@ -1,10 +1,11 @@
-import GlimmerComponent from "discourse/components/glimmer";
import I18n from "I18n";
-export default class UserMenuReviewableItem extends GlimmerComponent {
- constructor() {
- super(...arguments);
- this.reviewable = this.args.item;
+export default class ReviewableItemBase {
+ constructor({ reviewable, currentUser, siteSettings, site }) {
+ this.reviewable = reviewable;
+ this.currentUser = currentUser;
+ this.siteSettings = siteSettings;
+ this.site = site;
}
get actor() {
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-flagged-post-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js
similarity index 70%
rename from app/assets/javascripts/discourse/app/components/user-menu/reviewable-flagged-post-item.js
rename to app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js
index 215972e6ea7..6046f2bf9b3 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-flagged-post-item.js
+++ b/app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js
@@ -1,8 +1,8 @@
-import UserMenuDefaultReviewableItem from "discourse/components/user-menu/default-reviewable-item";
-import I18n from "I18n";
+import ReviewableItemBase from "discourse/lib/reviewable-items/base";
import { htmlSafe } from "@ember/template";
+import I18n from "I18n";
-export default class UserMenuReviewableFlaggedPostItem extends UserMenuDefaultReviewableItem {
+export default class extends ReviewableItemBase {
get description() {
const title = this.reviewable.topic_fancy_title;
const postNumber = this.reviewable.post_number;
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-queued-post-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js
similarity index 78%
rename from app/assets/javascripts/discourse/app/components/user-menu/reviewable-queued-post-item.js
rename to app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js
index 814fe109a2a..cfe53941f9b 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-queued-post-item.js
+++ b/app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js
@@ -1,10 +1,10 @@
-import UserMenuDefaultReviewableItem from "discourse/components/user-menu/default-reviewable-item";
-import I18n from "I18n";
+import ReviewableItemBase from "discourse/lib/reviewable-items/base";
import { htmlSafe } from "@ember/template";
import { escapeExpression } from "discourse/lib/utilities";
import { emojiUnescape } from "discourse/lib/text";
+import I18n from "I18n";
-export default class UserMenuReviewableQueuedPostItem extends UserMenuDefaultReviewableItem {
+export default class extends ReviewableItemBase {
get actor() {
return I18n.t("user_menu.reviewable.queue");
}
diff --git a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-user-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-items/user.js
similarity index 51%
rename from app/assets/javascripts/discourse/app/components/user-menu/reviewable-user-item.js
rename to app/assets/javascripts/discourse/app/lib/reviewable-items/user.js
index 3eb90dcdcee..fd4cfb12999 100644
--- a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-user-item.js
+++ b/app/assets/javascripts/discourse/app/lib/reviewable-items/user.js
@@ -1,7 +1,7 @@
-import UserMenuDefaultReviewableItem from "discourse/components/user-menu/default-reviewable-item";
+import ReviewableItemBase from "discourse/lib/reviewable-items/base";
import I18n from "I18n";
-export default class UserMenuReviewableUserItem extends UserMenuDefaultReviewableItem {
+export default class extends ReviewableItemBase {
get description() {
return I18n.t("user_menu.reviewable.suspicious_user", {
username: this.reviewable.username,
diff --git a/app/assets/javascripts/discourse/app/models/notification.js b/app/assets/javascripts/discourse/app/models/notification.js
index 4ef83473c81..6e917b91829 100644
--- a/app/assets/javascripts/discourse/app/models/notification.js
+++ b/app/assets/javascripts/discourse/app/models/notification.js
@@ -1,36 +1,6 @@
import RestModel from "discourse/models/rest";
import { tracked } from "@glimmer/tracking";
-const DEFAULT_ITEM = "user-menu/notification-item";
-
-function defaultComponentForType() {
- return {
- bookmark_reminder: "user-menu/bookmark-reminder-notification-item",
- custom: "user-menu/custom-notification-item",
- granted_badge: "user-menu/granted-badge-notification-item",
- group_mentioned: "user-menu/group-mentioned-notification-item",
- group_message_summary: "user-menu/group-message-summary-notification-item",
- invitee_accepted: "user-menu/invitee-accepted-notification-item",
- liked: "user-menu/liked-notification-item",
- liked_consolidated: "user-menu/liked-consolidated-notification-item",
- membership_request_accepted:
- "user-menu/membership-request-accepted-notification-item",
- membership_request_consolidated:
- "user-menu/membership-request-consolidated-notification-item",
- moved_post: "user-menu/moved-post-notification-item",
- watching_first_post: "user-menu/watching-first-post-notification-item",
- };
-}
-
-let _componentForType = defaultComponentForType();
-// TODO(osama): add plugin API
-
export default class Notification extends RestModel {
@tracked read;
-
- get userMenuComponent() {
- const component =
- _componentForType[this.site.notificationLookup[this.notification_type]];
- return component || DEFAULT_ITEM;
- }
}
diff --git a/app/assets/javascripts/discourse/app/models/user-menu-reviewable.js b/app/assets/javascripts/discourse/app/models/user-menu-reviewable.js
index 954c98772c6..42fe246ac24 100644
--- a/app/assets/javascripts/discourse/app/models/user-menu-reviewable.js
+++ b/app/assets/javascripts/discourse/app/models/user-menu-reviewable.js
@@ -1,18 +1,6 @@
import RestModel from "discourse/models/rest";
import { tracked } from "@glimmer/tracking";
-const DEFAULT_COMPONENT = "user-menu/default-reviewable-item";
-
-const DEFAULT_ITEM_COMPONENTS = {
- ReviewableFlaggedPost: "user-menu/reviewable-flagged-post-item",
- ReviewableQueuedPost: "user-menu/reviewable-queued-post-item",
- ReviewableUser: "user-menu/reviewable-user-item",
-};
-
export default class UserMenuReviewable extends RestModel {
@tracked pending;
-
- get userMenuComponent() {
- return DEFAULT_ITEM_COMPONENTS[this.type] || DEFAULT_COMPONENT;
- }
}
diff --git a/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js
new file mode 100644
index 00000000000..4f05a666cb9
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js
@@ -0,0 +1,19 @@
+import { getRenderDirector } from "discourse/lib/notification-item";
+import sessionFixtures from "discourse/tests/fixtures/session-fixtures";
+import User from "discourse/models/user";
+import Site from "discourse/models/site";
+
+export function createRenderDirector(
+ notification,
+ notificationType,
+ siteSettings
+) {
+ const director = getRenderDirector(
+ notificationType,
+ notification,
+ User.create(sessionFixtures["/session/current.json"].current_user),
+ siteSettings,
+ Site.current()
+ );
+ return director;
+}
diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
index 66591f376f9..5d16648c68d 100644
--- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
+++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js
@@ -72,6 +72,7 @@ import {
import { clearTagsHtmlCallbacks } from "discourse/lib/render-tags";
import { clearToolbarCallbacks } from "discourse/components/d-editor";
import { resetSidebarSection } from "discourse/lib/sidebar/custom-sections";
+import { resetRenderDirectorForNotifictaionTypes } from "discourse/lib/notification-item";
export function currentUser() {
return User.create(sessionFixtures["/session/current.json"].current_user);
@@ -200,6 +201,7 @@ export function testCleanup(container, app) {
clearTagsHtmlCallbacks();
clearToolbarCallbacks();
resetSidebarSection();
+ resetRenderDirectorForNotifictaionTypes();
}
export function discourseModule(name, options) {
diff --git a/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js
new file mode 100644
index 00000000000..4a0c30f1ced
--- /dev/null
+++ b/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js
@@ -0,0 +1,15 @@
+import { getRenderDirector } from "discourse/lib/reviewable-item";
+import sessionFixtures from "discourse/tests/fixtures/session-fixtures";
+import User from "discourse/models/user";
+import Site from "discourse/models/site";
+
+export function createRenderDirector(reviewable, reviewableType, siteSettings) {
+ const director = getRenderDirector(
+ reviewableType,
+ reviewable,
+ User.create(sessionFixtures["/session/current.json"].current_user),
+ siteSettings,
+ Site.current()
+ );
+ return director;
+}
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-reminder-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-reminder-notification-item-test.js
deleted file mode 100644
index 531c1b8429d..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-reminder-notification-item-test.js
+++ /dev/null
@@ -1,107 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import { render } from "@ember/test-helpers";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-import I18n from "I18n";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.bookmark_reminder,
- read: false,
- high_priority: true,
- created_at: "2022-07-01T06:00:32.173Z",
- post_number: 113,
- topic_id: 449,
- fancy_title: "This is fancy title <a>!",
- slug: "this-is-fancy-title",
- data: {
- title: "this is unsafe bookmark title !",
- display_username: "osama",
- bookmark_name: null,
- bookmarkable_url: "/t/sometopic/3232",
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | bookmark-reminder-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("when the bookmark has a name", async function (assert) {
- this.set(
- "notification",
- getNotification({ data: { bookmark_name: "MY BOOKMARK" } })
- );
- await render(template);
- const link = query("li a");
- assert.strictEqual(
- link.title,
- I18n.t("notifications.titles.bookmark_reminder_with_name", {
- name: "MY BOOKMARK",
- }),
- "the notification has a title that includes the bookmark name"
- );
- });
-
- test("when the bookmark doesn't have a name", async function (assert) {
- this.set(
- "notification",
- getNotification({ data: { bookmark_name: null } })
- );
- await render(template);
- const link = query("li a");
- assert.strictEqual(
- link.title,
- I18n.t("notifications.titles.bookmark_reminder"),
- "the notification has a generic title"
- );
- });
-
- test("when the bookmark reminder doesn't originate from a topic and has a title", async function (assert) {
- this.set(
- "notification",
- getNotification({
- post_number: null,
- topic_id: null,
- fancy_title: null,
- data: {
- title: "this is unsafe bookmark title !",
- bookmarkable_url: "/chat/channel/33",
- },
- })
- );
- await render(template);
- const description = query("li .notification-description");
- assert.strictEqual(
- description.textContent.trim(),
- "this is unsafe bookmark title !",
- "the title is rendered safely as description"
- );
- });
-
- test("when the bookmark reminder originates from a topic", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const description = query("li .notification-description");
- assert.strictEqual(
- description.textContent.trim(),
- "This is fancy title !",
- "fancy_title is safe and rendered correctly"
- );
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/granted-badge-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/granted-badge-notification-item-test.js
deleted file mode 100644
index abbe3a6fda4..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/granted-badge-notification-item-test.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { exists, query } from "discourse/tests/helpers/qunit-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import { render } from "@ember/test-helpers";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-import I18n from "I18n";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.granted_badge,
- read: false,
- high_priority: false,
- created_at: "2022-07-01T06:00:32.173Z",
- data: {
- badge_id: 12,
- badge_name: "Tough Guy ",
- badge_slug: "tough-guy",
- username: "ossa",
- badge_title: false,
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | granted-badge-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("links to the badge page and filters by the username", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const link = query("li a");
- assert.ok(link.href.endsWith("/badges/12/tough-guy?username=ossa"));
- });
-
- test("displays the right notification content", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const div = query("li div");
- assert.strictEqual(
- div.textContent.trim(),
- I18n.t("notifications.granted_badge", {
- description: "Tough Guy ",
- }),
- "label is rendered safely"
- );
- assert.ok(!exists("li .notification-label"));
- assert.ok(!exists("li .notification-description"));
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-mentioned-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-mentioned-notification-item-test.js
deleted file mode 100644
index 18b8ed3e0ad..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-mentioned-notification-item-test.js
+++ /dev/null
@@ -1,68 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { render } from "@ember/test-helpers";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.group_mentioned,
- read: false,
- high_priority: false,
- created_at: "2022-07-01T06:00:32.173Z",
- post_number: 113,
- topic_id: 449,
- fancy_title: "This is fancy title <a>!",
- slug: "this-is-fancy-title",
- data: {
- topic_title: "this is title before it becomes fancy !",
- original_post_id: 112,
- original_post_type: 1,
- original_username: "kolary",
- display_username: "osama",
- group_id: 333,
- group_name: "hikers",
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | group-mentioned-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("notification label displays the user who mentioned and the mentioned group", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const label = query("li .notification-label");
- assert.strictEqual(label.textContent.trim(), "osama @hikers");
- assert.ok(
- label.classList.contains("mention-group"),
- "label has mention-group class"
- );
- assert.ok(label.classList.contains("notify"), "label has notify class");
- });
-
- test("notification description displays the topic title", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const description = query("li .notification-description");
- assert.strictEqual(
- description.textContent.trim(),
- "This is fancy title !"
- );
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-message-summary-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-message-summary-notification-item-test.js
deleted file mode 100644
index 9b5a2989fd5..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/group-message-summary-notification-item-test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { exists, query } from "discourse/tests/helpers/qunit-helpers";
-import { render } from "@ember/test-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-import I18n from "I18n";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.group_message_summary,
- read: false,
- high_priority: false,
- created_at: "2022-07-01T06:00:32.173Z",
- data: {
- group_id: 321,
- group_name: "drummers",
- inbox_count: 13,
- username: "drummers.boss",
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | group-message-summary-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("the notification displays the right content", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const notification = query("li");
- assert.strictEqual(
- notification.textContent.trim(),
- I18n.t("notifications.group_message_summary", {
- count: 13,
- group_name: "drummers",
- })
- );
- assert.ok(!exists("li .notification-label"));
- assert.ok(!exists("li .notification-description"));
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-consolidated-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-consolidated-notification-item-test.js
deleted file mode 100644
index 0af3a813a07..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-consolidated-notification-item-test.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import { render } from "@ember/test-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-import I18n from "I18n";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.liked_consolidated,
- read: false,
- high_priority: false,
- created_at: "2022-07-01T06:00:32.173Z",
- data: {
- topic_title: "this is some topic and it's irrelevant",
- original_post_id: 3294,
- original_post_type: 1,
- original_username: "liker439",
- display_username: "liker439",
- username: "liker439",
- count: 44,
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | liked-consolidated-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("the notification links to the likes received notifications page of the user", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const link = query("li a");
- assert.ok(
- link.href.endsWith(
- "/u/eviltrout/notifications/likes-received?acting_username=liker439"
- )
- );
- });
-
- test("the notification label displays the user who liked", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const label = query("li .notification-label");
- assert.strictEqual(label.textContent.trim(), "liker439");
- });
-
- test("the notification description displays the number of likes", async function (assert) {
- this.set("notification", getNotification());
- await render(template);
- const description = query("li .notification-description");
- assert.strictEqual(
- description.textContent.trim(),
- I18n.t("notifications.liked_consolidated_description", { count: 44 })
- );
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-notification-item-test.js
deleted file mode 100644
index 679e21b1081..00000000000
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/liked-notification-item-test.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import { module, test } from "qunit";
-import { setupRenderingTest } from "discourse/tests/helpers/component-test";
-import { query } from "discourse/tests/helpers/qunit-helpers";
-import { render } from "@ember/test-helpers";
-import { deepMerge } from "discourse-common/lib/object";
-import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
-import Notification from "discourse/models/notification";
-import { hbs } from "ember-cli-htmlbars";
-import I18n from "I18n";
-
-function getNotification(overrides = {}) {
- return Notification.create(
- deepMerge(
- {
- id: 11,
- user_id: 1,
- notification_type: NOTIFICATION_TYPES.liked,
- read: false,
- high_priority: false,
- created_at: "2022-07-01T06:00:32.173Z",
- post_number: 113,
- topic_id: 449,
- fancy_title: "This is fancy title <a>!",
- slug: "this-is-fancy-title",
- data: {
- topic_title: "this is title before it becomes fancy !",
- username: "osama",
- display_username: "osama",
- username2: "shrek",
- count: 2,
- },
- },
- overrides
- )
- );
-}
-
-module(
- "Integration | Component | user-menu | liked-notification-item",
- function (hooks) {
- setupRenderingTest(hooks);
-
- const template = hbs``;
-
- test("when the likes count is 2", async function (assert) {
- this.set("notification", getNotification({ data: { count: 2 } }));
- await render(template);
-
- const label = query("li .notification-label");
- const description = query("li .notification-description");
- assert.strictEqual(
- label.textContent.trim(),
- "osama, shrek",
- "the label displays both usernames comma-concatenated"
- );
- assert.ok(
- label.classList.contains("double-user"),
- "label has double-user class"
- );
- assert.strictEqual(
- description.textContent.trim(),
- "This is fancy title !",
- "the description displays the topic title"
- );
- });
-
- test("when the likes count is more than 2", async function (assert) {
- this.set("notification", getNotification({ data: { count: 3 } }));
- await render(template);
-
- const label = query("li .notification-label");
- const description = query("li .notification-description");
- assert.strictEqual(
- label.textContent.trim(),
- I18n.t("notifications.liked_by_multiple_users", {
- username: "osama",
- username2: "shrek",
- count: 1,
- }),
- "the label displays the first 2 usernames comma-concatenated with the count of remaining users"
- );
- assert.ok(
- label.classList.contains("multi-user"),
- "label has multi-user class"
- );
- assert.strictEqual(
- description.textContent.trim(),
- "This is fancy title !",
- "the description displays the topic title"
- );
- });
-
- test("when the likes count is 1", async function (assert) {
- this.set(
- "notification",
- getNotification({ data: { count: 1, username2: null } })
- );
- await render(template);
-
- const label = query("li .notification-label");
- const description = query("li .notification-description");
- assert.strictEqual(
- label.textContent.trim(),
- "osama",
- "the label displays the username"
- );
- assert.strictEqual(
- description.textContent.trim(),
- "This is fancy title !",
- "the description displays the topic title"
- );
- });
- }
-);
diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/notification-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/notification-item-test.js
index 5c25b81bd8a..3198d73921a 100644
--- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/notification-item-test.js
+++ b/app/assets/javascripts/discourse/tests/integration/components/user-menu/notification-item-test.js
@@ -1,11 +1,12 @@
import { module, test } from "qunit";
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
-import { render, settled } from "@ember/test-helpers";
+import { click, render, settled } from "@ember/test-helpers";
import { deepMerge } from "discourse-common/lib/object";
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
import Notification from "discourse/models/notification";
import { hbs } from "ember-cli-htmlbars";
+import { withPluginApi } from "discourse/lib/plugin-api";
import I18n from "I18n";
function getNotification(overrides = {}) {
@@ -194,5 +195,202 @@ module(
);
assert.ok(!query("img"), "no exists");
});
+
+ test("various aspects can be customized according to the notification's render director", async function (assert) {
+ withPluginApi("0.1", (api) => {
+ api.registerNotificationTypeRenderer(
+ "linked",
+ (NotificationItemBase) => {
+ return class extends NotificationItemBase {
+ get classNames() {
+ return ["additional", "classes"];
+ }
+
+ get linkHref() {
+ return "/somewhere/awesome";
+ }
+
+ get linkTitle() {
+ return "hello world this is unsafe '\"";
+ }
+
+ get icon() {
+ return "wrench";
+ }
+
+ get label() {
+ return "notification label 666 ";
+ }
+
+ get description() {
+ return "notification description 123