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