diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.hbs b/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.hbs deleted file mode 100644 index e0829c1c076..00000000000 --- a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.hbs +++ /dev/null @@ -1 +0,0 @@ -{{component this.component item=@item}} diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js deleted file mode 100644 index cc5679bfb31..00000000000 --- a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-notification-item.js +++ /dev/null @@ -1,12 +0,0 @@ -import Component from "@glimmer/component"; -import Notification from "discourse/models/notification"; - -export default class UserMenuBookmarkNotificationItem extends Component { - get component() { - if (this.args.item.constructor === Notification) { - return "user-menu/notification-item"; - } else { - return "user-menu/bookmark-item"; - } - } -} diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list-empty-state.js b/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list-empty-state.js new file mode 100644 index 00000000000..742bb3c18ef --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list-empty-state.js @@ -0,0 +1,3 @@ +import templateOnly from "@ember/component/template-only"; + +export default templateOnly(); diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list.js b/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list.js index 639b30cc404..add65403229 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/bookmarks-list.js @@ -3,6 +3,8 @@ import { ajax } from "discourse/lib/ajax"; import Notification from "discourse/models/notification"; import showModal from "discourse/lib/show-modal"; import I18n from "I18n"; +import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; +import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item"; export default class UserMenuBookmarksList extends UserMenuNotificationsList { get dismissTypes() { @@ -29,10 +31,6 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList { return "user-menu-bookmarks-tab"; } - get itemComponent() { - return "user-menu/bookmark-notification-item"; - } - get emptyStateComponent() { return "user-menu/bookmarks-list-empty-state"; } @@ -50,10 +48,22 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList { return ajax(`/u/${this.currentUser.username}/user-menu-bookmarks`).then( (data) => { const content = []; - data.notifications.forEach((notification) => { - content.push(Notification.create(notification)); + data.notifications.forEach((rawNotification) => { + const notification = Notification.create(rawNotification); + content.push( + new UserMenuNotificationItem({ + notification, + currentUser: this.currentUser, + siteSettings: this.siteSettings, + site: this.site, + }) + ); }); - content.push(...data.bookmarks); + content.push( + ...data.bookmarks.map((bookmark) => { + return new UserMenuBookmarkItem({ bookmark }); + }) + ); return content; } ); diff --git a/app/assets/javascripts/discourse/app/components/user-menu/items-list-empty-state.js b/app/assets/javascripts/discourse/app/components/user-menu/items-list-empty-state.js new file mode 100644 index 00000000000..742bb3c18ef --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-menu/items-list-empty-state.js @@ -0,0 +1,3 @@ +import templateOnly from "@ember/component/template-only"; + +export default templateOnly(); 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 fab0afadc89..436bee4b915 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 b0fc9039860..7ab92d8fabe 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 @@ -9,7 +9,7 @@ export default class UserMenuItemsList extends Component { constructor() { super(...arguments); - this._load(); + this.#load(); } get itemsCacheKey() {} @@ -28,12 +28,6 @@ export default class UserMenuItemsList extends Component { 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}` @@ -41,15 +35,15 @@ export default class UserMenuItemsList extends Component { } refreshList() { - this._load(); + this.#load(); } dismissWarningModal() { return null; } - _load() { - const cached = this._getCachedItems(); + #load() { + const cached = this.#getCachedItems(); if (cached?.length) { this.items = cached; } else { @@ -57,20 +51,20 @@ export default class UserMenuItemsList extends Component { } this.fetchItems() .then((items) => { - this._setCachedItems(items); + this.#setCachedItems(items); this.items = items; }) .finally(() => (this.loading = false)); } - _getCachedItems() { + #getCachedItems() { const key = this.itemsCacheKey; if (key) { return Session.currentProp(`user-menu-items:${key}`); } } - _setCachedItems(newItems) { + #setCachedItems(newItems) { const key = this.itemsCacheKey; if (key) { Session.currentProp(`user-menu-items:${key}`, newItems); diff --git a/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js b/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js index c16a35f8a77..62dca80f57f 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/menu-item.js @@ -1,35 +1,60 @@ import Component from "@glimmer/component"; +import { emojiUnescape } from "discourse/lib/text"; +import { escapeExpression } from "discourse/lib/utilities"; +import { htmlSafe } from "@ember/template"; import { action } from "@ember/object"; export default class UserMenuItem extends Component { - get className() {} + get className() { + return this.#item.className; + } get linkHref() { - throw new Error("not implemented"); + return this.#item.linkHref; } get linkTitle() { - throw new Error("not implemented"); + return this.#item.linkTitle; } get icon() { - throw new Error("not implemented"); + return this.#item.icon; } get label() { - throw new Error("not implemented"); + return this.#item.label; } - get labelClass() {} + get labelClass() { + return this.#item.labelClass; + } get description() { - throw new Error("not implemented"); + const description = this.#item.description; + if (description) { + if (typeof description === "string") { + // do emoji unescape on all items + return htmlSafe(emojiUnescape(escapeExpression(description))); + } + // it's probably an htmlSafe object, don't try to unescape emojis + return description; + } } - get descriptionClass() {} + get descriptionClass() { + return this.#item.descriptionClass; + } - get topicId() {} + get topicId() { + return this.#item.topicId; + } + + get #item() { + return this.args.item; + } @action - onClick() {} + onClick() { + return this.#item.onClick(); + } } diff --git a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.hbs b/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.hbs deleted file mode 100644 index e0829c1c076..00000000000 --- a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.hbs +++ /dev/null @@ -1 +0,0 @@ -{{component this.component item=@item}} diff --git a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js b/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js deleted file mode 100644 index 1726851d27b..00000000000 --- a/app/assets/javascripts/discourse/app/components/user-menu/message-notification-item.js +++ /dev/null @@ -1,12 +0,0 @@ -import Component from "@glimmer/component"; -import Notification from "discourse/models/notification"; - -export default class UserMenuMessageNotificationItem extends Component { - get component() { - if (this.args.item.constructor === Notification) { - return "user-menu/notification-item"; - } else { - return "user-menu/message-item"; - } - } -} diff --git a/app/assets/javascripts/discourse/app/components/user-menu/messages-list-empty-state.js b/app/assets/javascripts/discourse/app/components/user-menu/messages-list-empty-state.js new file mode 100644 index 00000000000..742bb3c18ef --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/user-menu/messages-list-empty-state.js @@ -0,0 +1,3 @@ +import templateOnly from "@ember/component/template-only"; + +export default templateOnly(); diff --git a/app/assets/javascripts/discourse/app/components/user-menu/messages-list.js b/app/assets/javascripts/discourse/app/components/user-menu/messages-list.js index cfc7a83e986..92364a36693 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/messages-list.js +++ b/app/assets/javascripts/discourse/app/components/user-menu/messages-list.js @@ -3,6 +3,8 @@ import { ajax } from "discourse/lib/ajax"; import Notification from "discourse/models/notification"; import showModal from "discourse/lib/show-modal"; import I18n from "I18n"; +import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; +import UserMenuMessageItem from "discourse/lib/user-menu/message-item"; export default class UserMenuMessagesList extends UserMenuNotificationsList { get dismissTypes() { @@ -29,10 +31,6 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList { return "user-menu-messages-tab"; } - get itemComponent() { - return "user-menu/message-notification-item"; - } - get emptyStateComponent() { return "user-menu/messages-list-empty-state"; } @@ -51,10 +49,22 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList { `/u/${this.currentUser.username}/user-menu-private-messages` ).then((data) => { const content = []; - data.notifications.forEach((notification) => { - content.push(Notification.create(notification)); + data.notifications.forEach((rawNotification) => { + const notification = Notification.create(rawNotification); + content.push( + new UserMenuNotificationItem({ + notification, + currentUser: this.currentUser, + siteSettings: this.siteSettings, + site: this.site, + }) + ); }); - content.push(...data.topics); + content.push( + ...data.topics.map((topic) => { + return new UserMenuMessageItem({ message: topic }); + }) + ); return content; }); } 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 7604fda2ff9..78fe44213a0 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 @@ -5,9 +5,11 @@ import { ajax } from "discourse/lib/ajax"; import { postRNWebviewMessage } from "discourse/lib/utilities"; import showModal from "discourse/lib/show-modal"; import { inject as service } from "@ember/service"; +import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; export default class UserMenuNotificationsList extends UserMenuItemsList { @service currentUser; + @service siteSettings; @service site; @service store; @@ -28,7 +30,7 @@ export default class UserMenuNotificationsList extends UserMenuItemsList { } get showDismiss() { - return this.items.some((item) => !item.read); + return this.items.some((item) => !item.notification.read); } get dismissTitle() { @@ -52,10 +54,6 @@ export default class UserMenuNotificationsList extends UserMenuItemsList { } } - get itemComponent() { - return "user-menu/notification-item"; - } - fetchItems() { const params = { limit: 30, @@ -72,7 +70,16 @@ export default class UserMenuNotificationsList extends UserMenuItemsList { return this.store .findStale("notification", params) .refresh() - .then((c) => c.content); + .then((c) => { + return c.content.map((notification) => { + return new UserMenuNotificationItem({ + notification, + currentUser: this.currentUser, + siteSettings: this.siteSettings, + site: this.site, + }); + }); + }); } dismissWarningModal() { 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 2b1be914328..d8155ef6c88 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 @@ -3,8 +3,14 @@ import { ajax } from "discourse/lib/ajax"; import UserMenuReviewable from "discourse/models/user-menu-reviewable"; import I18n from "I18n"; import getUrl from "discourse-common/lib/get-url"; +import UserMenuReviewableItem from "discourse/lib/user-menu/reviewable-item"; +import { inject as service } from "@ember/service"; export default class UserMenuReviewablesList extends UserMenuItemsList { + @service currentUser; + @service siteSettings; + @service site; + get showAllHref() { return getUrl("/review"); } @@ -17,14 +23,15 @@ 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) => { - return UserMenuReviewable.create(item); + return new UserMenuReviewableItem({ + reviewable: UserMenuReviewable.create(item), + currentUser: this.currentUser, + siteSettings: this.siteSettings, + site: this.site, + }); }); }); } 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 deleted file mode 100644 index abcecc3b7ca..00000000000 --- a/app/assets/javascripts/discourse/app/lib/notification-items/watching-first-post.js +++ /dev/null @@ -1,8 +0,0 @@ -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/notification-item.js b/app/assets/javascripts/discourse/app/lib/notification-types-manager.js similarity index 57% rename from app/assets/javascripts/discourse/app/lib/notification-item.js rename to app/assets/javascripts/discourse/app/lib/notification-types-manager.js index f36399bc6d5..c7a58bee040 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-item.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types-manager.js @@ -1,17 +1,17 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/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"; +import BookmarkReminder from "discourse/lib/notification-types/bookmark-reminder"; +import Custom from "discourse/lib/notification-types/custom"; +import GrantedBadge from "discourse/lib/notification-types/granted-badge"; +import GroupMentioned from "discourse/lib/notification-types/group-mentioned"; +import GroupMessageSummary from "discourse/lib/notification-types/group-message-summary"; +import InviteeAccepted from "discourse/lib/notification-types/invitee-accepted"; +import LikedConsolidated from "discourse/lib/notification-types/liked-consolidated"; +import Liked from "discourse/lib/notification-types/liked"; +import MembershipRequestAccepted from "discourse/lib/notification-types/membership-request-accepted"; +import MembershipRequestConsolidated from "discourse/lib/notification-types/membership-request-consolidated"; +import MovedPost from "discourse/lib/notification-types/moved-post"; +import WatchingFirstPost from "discourse/lib/notification-types/watching-first-post"; const CLASS_FOR_TYPE = { bookmark_reminder: BookmarkReminder, @@ -31,7 +31,7 @@ const CLASS_FOR_TYPE = { let _customClassForType = {}; export function registerNotificationTypeRenderer(notificationType, func) { - _customClassForType[notificationType] = func(NotificationItemBase); + _customClassForType[notificationType] = func(NotificationTypeBase); } export function resetNotificationTypeRenderers() { @@ -46,6 +46,6 @@ export function getRenderDirector( site ) { const klass = - _customClassForType[type] || CLASS_FOR_TYPE[type] || NotificationItemBase; + _customClassForType[type] || CLASS_FOR_TYPE[type] || NotificationTypeBase; 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-types/base.js similarity index 88% rename from app/assets/javascripts/discourse/app/lib/notification-items/base.js rename to app/assets/javascripts/discourse/app/lib/notification-types/base.js index fb7a6961019..579445405c2 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/base.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/base.js @@ -4,7 +4,7 @@ import { emojiUnescape } from "discourse/lib/text"; import { htmlSafe } from "@ember/template"; import I18n from "I18n"; -export default class NotificationItemBase { +export default class NotificationTypeBase { constructor({ notification, currentUser, siteSettings, site }) { this.notification = notification; this.currentUser = currentUser; @@ -16,7 +16,19 @@ export default class NotificationItemBase { * @returns {string[]} An array of addtional classes that should be added to the
  • element of the notification item. */ get classNames() { - return []; + const classes = ["notification"]; + if (this.notification.read) { + classes.push("read"); + } else { + classes.push("unread"); + } + if (this.notificationName) { + classes.push(this.notificationName.replace(/_/g, "-")); + } + if (this.notification.is_warning) { + classes.push("is-warning"); + } + return classes; } /** @@ -76,11 +88,6 @@ export default class NotificationItemBase { } } - /** - * Function that is called when the notification item is clicked. - */ - onClick() {} - /** * @returns {string[]} Include additional classes to the label. */ diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js b/app/assets/javascripts/discourse/app/lib/notification-types/bookmark-reminder.js similarity index 83% rename from app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js rename to app/assets/javascripts/discourse/app/lib/notification-types/bookmark-reminder.js index 60318963539..b3074b148f3 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/bookmark-reminder.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/bookmark-reminder.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import I18n from "I18n"; import getUrl from "discourse-common/lib/get-url"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { 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/lib/notification-items/custom.js b/app/assets/javascripts/discourse/app/lib/notification-types/custom.js similarity index 67% rename from app/assets/javascripts/discourse/app/lib/notification-items/custom.js rename to app/assets/javascripts/discourse/app/lib/notification-types/custom.js index 09006e37a87..7096f79d6f7 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/custom.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/custom.js @@ -1,7 +1,7 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get linkTitle() { if (this.notification.data.title) { return I18n.t(this.notification.data.title); diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js b/app/assets/javascripts/discourse/app/lib/notification-types/granted-badge.js similarity index 86% rename from app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js rename to app/assets/javascripts/discourse/app/lib/notification-types/granted-badge.js index 13e6b2ff31a..2fdad51637f 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/granted-badge.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/granted-badge.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import getURL from "discourse-common/lib/get-url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get linkHref() { const badgeId = this.notification.data.badge_id; if (badgeId) { diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js b/app/assets/javascripts/discourse/app/lib/notification-types/group-mentioned.js similarity index 55% rename from app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js rename to app/assets/javascripts/discourse/app/lib/notification-types/group-mentioned.js index b9ba3ee4c5b..6256eda6869 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/group-mentioned.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/group-mentioned.js @@ -1,6 +1,6 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get label() { return `${this.username} @${this.notification.data.group_name}`; } diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js b/app/assets/javascripts/discourse/app/lib/notification-types/group-message-summary.js similarity index 78% rename from app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js rename to app/assets/javascripts/discourse/app/lib/notification-types/group-message-summary.js index 01c91e0897a..1d97bf50aec 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/group-message-summary.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/group-message-summary.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { userPath } from "discourse/lib/url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get description() { return I18n.t("notifications.group_message_summary", { count: this.notification.data.inbox_count, diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js b/app/assets/javascripts/discourse/app/lib/notification-types/invitee-accepted.js similarity index 66% rename from app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js rename to app/assets/javascripts/discourse/app/lib/notification-types/invitee-accepted.js index 4761a6f4321..4f1e413ff38 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/invitee-accepted.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/invitee-accepted.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { userPath } from "discourse/lib/url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { 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-types/liked-consolidated.js similarity index 77% rename from app/assets/javascripts/discourse/app/lib/notification-items/liked-consolidated.js rename to app/assets/javascripts/discourse/app/lib/notification-types/liked-consolidated.js index 639fc1a1ab2..8f2494927cc 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/liked-consolidated.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/liked-consolidated.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { userPath } from "discourse/lib/url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get linkHref() { // TODO(osama): serialize username with notifications return userPath( diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/liked.js b/app/assets/javascripts/discourse/app/lib/notification-types/liked.js similarity index 86% rename from app/assets/javascripts/discourse/app/lib/notification-items/liked.js rename to app/assets/javascripts/discourse/app/lib/notification-types/liked.js index e41376b0dae..0129ed5130a 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/liked.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/liked.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { formatUsername } from "discourse/lib/utilities"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get label() { if (this.count === 2) { return I18n.t("notifications.liked_by_2_users", { diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js b/app/assets/javascripts/discourse/app/lib/notification-types/membership-request-accepted.js similarity index 73% rename from app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js rename to app/assets/javascripts/discourse/app/lib/notification-types/membership-request-accepted.js index d32ec41f271..71ef2af8f0a 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-accepted.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/membership-request-accepted.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { groupPath } from "discourse/lib/url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get linkHref() { return groupPath(this.notification.data.group_name); } diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js b/app/assets/javascripts/discourse/app/lib/notification-types/membership-request-consolidated.js similarity index 77% rename from app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js rename to app/assets/javascripts/discourse/app/lib/notification-types/membership-request-consolidated.js index 0f52b5ea031..4b71deed693 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/membership-request-consolidated.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/membership-request-consolidated.js @@ -1,8 +1,8 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import { userPath } from "discourse/lib/url"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get linkHref() { return userPath( `${this.notification.username || this.currentUser.username}/messages` diff --git a/app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js b/app/assets/javascripts/discourse/app/lib/notification-types/moved-post.js similarity index 50% rename from app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js rename to app/assets/javascripts/discourse/app/lib/notification-types/moved-post.js index 58ed1bc37ef..b865f4c934d 100644 --- a/app/assets/javascripts/discourse/app/lib/notification-items/moved-post.js +++ b/app/assets/javascripts/discourse/app/lib/notification-types/moved-post.js @@ -1,7 +1,7 @@ -import NotificationItemBase from "discourse/lib/notification-items/base"; +import NotificationTypeBase from "discourse/lib/notification-types/base"; import I18n from "I18n"; -export default class extends NotificationItemBase { +export default class extends NotificationTypeBase { get label() { return I18n.t("notifications.user_moved_post", { username: this.username }); } diff --git a/app/assets/javascripts/discourse/app/lib/notification-types/watching-first-post.js b/app/assets/javascripts/discourse/app/lib/notification-types/watching-first-post.js new file mode 100644 index 00000000000..c2df9b89478 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/notification-types/watching-first-post.js @@ -0,0 +1,8 @@ +import NotificationTypeBase from "discourse/lib/notification-types/base"; +import I18n from "I18n"; + +export default class extends NotificationTypeBase { + 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 437f8bb4f30..aed69342ce8 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -98,7 +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"; +import { registerNotificationTypeRenderer } from "discourse/lib/notification-types-manager"; import { registerUserMenuTab } from "discourse/lib/user-menu/tab"; // If you add any methods to the API ensure you bump up the version number @@ -1857,12 +1857,12 @@ class PluginApi { /** * 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 + * renderer of an existing type. See lib/notification-types/base.js for * documentation and the default renderer. * * ``` - * api.registerNotificationTypeRenderer("your_notification_type", (NotificationItemBase) => { - * return class extends NotificationItemBase { + * api.registerNotificationTypeRenderer("your_notification_type", (NotificationTypeBase) => { + * return class extends NotificationTypeBase { * get label() { * return "some label"; * } @@ -1874,8 +1874,8 @@ class PluginApi { * }); * ``` * @callback renderDirectorRegistererCallback - * @param {NotificationItemBase} The base class from which the returned class should inherit. - * @returns {NotificationItemBase} A class that inherits from NotificationItemBase. + * @param {NotificationTypeBase} The base class from which the returned class should inherit. + * @returns {NotificationTypeBase} A class that inherits from NotificationTypeBase. * * @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. diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-item.js b/app/assets/javascripts/discourse/app/lib/reviewable-item.js deleted file mode 100644 index 7ef29025043..00000000000 --- a/app/assets/javascripts/discourse/app/lib/reviewable-item.js +++ /dev/null @@ -1,22 +0,0 @@ -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/lib/reviewable-types-manager.js b/app/assets/javascripts/discourse/app/lib/reviewable-types-manager.js new file mode 100644 index 00000000000..65988330ec9 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/reviewable-types-manager.js @@ -0,0 +1,22 @@ +import ReviewableTypeBase from "discourse/lib/reviewable-types/base"; + +import FlaggedPost from "discourse/lib/reviewable-types/flagged-post"; +import QueuedPost from "discourse/lib/reviewable-types/queued-post"; +import ReviewableUser from "discourse/lib/reviewable-types/user"; + +const CLASS_FOR_TYPE = { + ReviewableFlaggedPost: FlaggedPost, + ReviewableQueuedPost: QueuedPost, + ReviewableUser, +}; + +export function getRenderDirector( + type, + reviewable, + currentUser, + siteSettings, + site +) { + const klass = CLASS_FOR_TYPE[type] || ReviewableTypeBase; + return new klass({ reviewable, currentUser, siteSettings, site }); +} diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-items/base.js b/app/assets/javascripts/discourse/app/lib/reviewable-types/base.js similarity index 93% rename from app/assets/javascripts/discourse/app/lib/reviewable-items/base.js rename to app/assets/javascripts/discourse/app/lib/reviewable-types/base.js index b8a71c36212..58cfe9f360b 100644 --- a/app/assets/javascripts/discourse/app/lib/reviewable-items/base.js +++ b/app/assets/javascripts/discourse/app/lib/reviewable-types/base.js @@ -1,6 +1,6 @@ import I18n from "I18n"; -export default class ReviewableItemBase { +export default class ReviewableTypeBase { constructor({ reviewable, currentUser, siteSettings, site }) { this.reviewable = reviewable; this.currentUser = currentUser; diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js b/app/assets/javascripts/discourse/app/lib/reviewable-types/flagged-post.js similarity index 80% rename from app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js rename to app/assets/javascripts/discourse/app/lib/reviewable-types/flagged-post.js index 6046f2bf9b3..d0a49dc323d 100644 --- a/app/assets/javascripts/discourse/app/lib/reviewable-items/flagged-post.js +++ b/app/assets/javascripts/discourse/app/lib/reviewable-types/flagged-post.js @@ -1,8 +1,8 @@ -import ReviewableItemBase from "discourse/lib/reviewable-items/base"; +import ReviewableTypeBase from "discourse/lib/reviewable-types/base"; import { htmlSafe } from "@ember/template"; import I18n from "I18n"; -export default class extends ReviewableItemBase { +export default class extends ReviewableTypeBase { get description() { const title = this.reviewable.topic_fancy_title; const postNumber = this.reviewable.post_number; diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js b/app/assets/javascripts/discourse/app/lib/reviewable-types/queued-post.js similarity index 86% rename from app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js rename to app/assets/javascripts/discourse/app/lib/reviewable-types/queued-post.js index cfe53941f9b..8ef928b54e1 100644 --- a/app/assets/javascripts/discourse/app/lib/reviewable-items/queued-post.js +++ b/app/assets/javascripts/discourse/app/lib/reviewable-types/queued-post.js @@ -1,10 +1,10 @@ -import ReviewableItemBase from "discourse/lib/reviewable-items/base"; +import ReviewableTypeBase from "discourse/lib/reviewable-types/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 extends ReviewableItemBase { +export default class extends ReviewableTypeBase { get actor() { return I18n.t("user_menu.reviewable.queue"); } diff --git a/app/assets/javascripts/discourse/app/lib/reviewable-items/user.js b/app/assets/javascripts/discourse/app/lib/reviewable-types/user.js similarity index 64% rename from app/assets/javascripts/discourse/app/lib/reviewable-items/user.js rename to app/assets/javascripts/discourse/app/lib/reviewable-types/user.js index fd4cfb12999..b3d7f1040ef 100644 --- a/app/assets/javascripts/discourse/app/lib/reviewable-items/user.js +++ b/app/assets/javascripts/discourse/app/lib/reviewable-types/user.js @@ -1,7 +1,7 @@ -import ReviewableItemBase from "discourse/lib/reviewable-items/base"; +import ReviewableTypeBase from "discourse/lib/reviewable-types/base"; import I18n from "I18n"; -export default class extends ReviewableItemBase { +export default class extends ReviewableTypeBase { get description() { return I18n.t("user_menu.reviewable.suspicious_user", { username: this.reviewable.username, diff --git a/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js new file mode 100644 index 00000000000..39baad43981 --- /dev/null +++ b/app/assets/javascripts/discourse/app/lib/user-menu/base-item.js @@ -0,0 +1,29 @@ +export default class UserMenuBaseItem { + get className() {} + + get linkHref() { + throw new Error("not implemented"); + } + + get linkTitle() { + throw new Error("not implemented"); + } + + get icon() { + throw new Error("not implemented"); + } + + get label() { + throw new Error("not implemented"); + } + + get labelClass() {} + + get description() { + throw new Error("not implemented"); + } + + get descriptionClass() {} + + get topicId() {} +} diff --git a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/bookmark-item.js similarity index 68% rename from app/assets/javascripts/discourse/app/components/user-menu/bookmark-item.js rename to app/assets/javascripts/discourse/app/lib/user-menu/bookmark-item.js index 4c0d2583eb2..9f69b04f10a 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/bookmark-item.js +++ b/app/assets/javascripts/discourse/app/lib/user-menu/bookmark-item.js @@ -1,7 +1,12 @@ -import UserMenuItem from "discourse/components/user-menu/menu-item"; +import UserMenuBaseItem from "discourse/lib/user-menu/base-item"; import { NO_REMINDER_ICON } from "discourse/models/bookmark"; -export default class UserMenuBookmarkItem extends UserMenuItem { +export default class UserMenuBookmarkItem extends UserMenuBaseItem { + constructor({ bookmark }) { + super(...arguments); + this.bookmark = bookmark; + } + get className() { return "bookmark"; } @@ -29,8 +34,4 @@ export default class UserMenuBookmarkItem extends UserMenuItem { get topicId() { return this.bookmark.topic_id; } - - get bookmark() { - return this.args.item; - } } diff --git a/app/assets/javascripts/discourse/app/components/user-menu/message-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/message-item.js similarity index 67% rename from app/assets/javascripts/discourse/app/components/user-menu/message-item.js rename to app/assets/javascripts/discourse/app/lib/user-menu/message-item.js index 81cc8151c07..d42909d14da 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/message-item.js +++ b/app/assets/javascripts/discourse/app/lib/user-menu/message-item.js @@ -1,9 +1,15 @@ -import UserMenuItem from "discourse/components/user-menu/menu-item"; +import UserMenuBaseItem from "discourse/lib/user-menu/base-item"; import { postUrl } from "discourse/lib/utilities"; import { htmlSafe } from "@ember/template"; +import { emojiUnescape } from "discourse/lib/text"; import I18n from "I18n"; -export default class UserMenuMessageItem extends UserMenuItem { +export default class UserMenuMessageItem extends UserMenuBaseItem { + constructor({ message }) { + super(...arguments); + this.message = message; + } + get className() { return "message"; } @@ -29,14 +35,10 @@ export default class UserMenuMessageItem extends UserMenuItem { } get description() { - return htmlSafe(this.message.fancy_title); + return htmlSafe(emojiUnescape(this.message.fancy_title)); } get topicId() { return this.message.id; } - - get message() { - return this.args.item; - } } diff --git a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/notification-item.js similarity index 53% rename from app/assets/javascripts/discourse/app/components/user-menu/notification-item.js rename to app/assets/javascripts/discourse/app/lib/user-menu/notification-item.js index ffb41c123d8..1ca293407e1 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/notification-item.js +++ b/app/assets/javascripts/discourse/app/lib/user-menu/notification-item.js @@ -1,45 +1,28 @@ -import UserMenuItem from "discourse/components/user-menu/menu-item"; -import { setTransientHeader } from "discourse/lib/ajax"; -import { action } from "@ember/object"; -import { getRenderDirector } from "discourse/lib/notification-item"; -import getURL from "discourse-common/lib/get-url"; +import UserMenuBaseItem from "discourse/lib/user-menu/base-item"; import cookie from "discourse/lib/cookie"; -import { inject as service } from "@ember/service"; +import getURL from "discourse-common/lib/get-url"; +import { setTransientHeader } from "discourse/lib/ajax"; +import { getRenderDirector } from "discourse/lib/notification-types-manager"; -export default class UserMenuNotificationItem extends UserMenuItem { - @service currentUser; - @service siteSettings; - @service site; - - constructor() { +export default class UserMenuNotificationItem extends UserMenuBaseItem { + constructor({ notification, currentUser, siteSettings, site }) { super(...arguments); + this.notification = notification; + this.currentUser = currentUser; + this.siteSettings = siteSettings; + this.site = site; + this.renderDirector = getRenderDirector( this.#notificationName, - this.notification, - this.currentUser, - this.siteSettings, - this.site + notification, + currentUser, + siteSettings, + site ); } get className() { - const classes = ["notification"]; - if (this.notification.read) { - classes.push("read"); - } else { - classes.push("unread"); - } - 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(" "); + return this.renderDirector.classNames?.join(" ") || ""; } get linkHref() { @@ -74,21 +57,15 @@ export default class UserMenuNotificationItem extends UserMenuItem { return this.notification.topic_id; } - get notification() { - return this.args.item; - } - get #notificationName() { return this.site.notificationLookup[this.notification.notification_type]; } - @action onClick() { if (!this.notification.read) { this.notification.set("read", true); 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/reviewable-item.js b/app/assets/javascripts/discourse/app/lib/user-menu/reviewable-item.js similarity index 66% rename from app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js rename to app/assets/javascripts/discourse/app/lib/user-menu/reviewable-item.js index e10d64a35cd..abdd2e2e637 100644 --- a/app/assets/javascripts/discourse/app/components/user-menu/reviewable-item.js +++ b/app/assets/javascripts/discourse/app/lib/user-menu/reviewable-item.js @@ -1,16 +1,15 @@ -import UserMenuItem from "discourse/components/user-menu/menu-item"; +import UserMenuBaseItem from "discourse/lib/user-menu/base-item"; import getURL from "discourse-common/lib/get-url"; -import { getRenderDirector } from "discourse/lib/reviewable-item"; -import { inject as service } from "@ember/service"; +import { getRenderDirector } from "discourse/lib/reviewable-types-manager"; -export default class UserMenuReviewableItem extends UserMenuItem { - @service currentUser; - @service siteSettings; - @service site; - - constructor() { +export default class UserMenuReviewableItem extends UserMenuBaseItem { + constructor({ reviewable, currentUser, siteSettings, site }) { super(...arguments); - this.reviewable = this.args.item; + this.reviewable = reviewable; + this.currentUser = currentUser; + this.siteSettings = siteSettings; + this.site = site; + this.renderDirector = getRenderDirector( this.reviewable.type, this.reviewable, diff --git a/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js similarity index 85% rename from app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js rename to app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js index 4f05a666cb9..5c9c10481e9 100644 --- a/app/assets/javascripts/discourse/tests/helpers/notification-items-helper.js +++ b/app/assets/javascripts/discourse/tests/helpers/notification-types-helper.js @@ -1,4 +1,4 @@ -import { getRenderDirector } from "discourse/lib/notification-item"; +import { getRenderDirector } from "discourse/lib/notification-types-manager"; import sessionFixtures from "discourse/tests/fixtures/session-fixtures"; import User from "discourse/models/user"; import Site from "discourse/models/site"; diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index c23824b4557..332ca5748d2 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -73,7 +73,7 @@ import { clearTagsHtmlCallbacks } from "discourse/lib/render-tags"; import { clearToolbarCallbacks } from "discourse/components/d-editor"; import { clearExtraHeaderIcons } from "discourse/widgets/header"; import { resetSidebarSection } from "discourse/lib/sidebar/custom-sections"; -import { resetNotificationTypeRenderers } from "discourse/lib/notification-item"; +import { resetNotificationTypeRenderers } from "discourse/lib/notification-types-manager"; import { resetUserMenuTabs } from "discourse/lib/user-menu/tab"; export function currentUser() { diff --git a/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js b/app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js similarity index 85% rename from app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js rename to app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js index 4a0c30f1ced..5a0b2fc528c 100644 --- a/app/assets/javascripts/discourse/tests/helpers/reviewable-items-helper.js +++ b/app/assets/javascripts/discourse/tests/helpers/reviewable-types-helper.js @@ -1,4 +1,4 @@ -import { getRenderDirector } from "discourse/lib/reviewable-item"; +import { getRenderDirector } from "discourse/lib/reviewable-types-manager"; import sessionFixtures from "discourse/tests/fixtures/session-fixtures"; import User from "discourse/models/user"; import Site from "discourse/models/site"; diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-item-test.js deleted file mode 100644 index 8a6d98c2dfb..00000000000 --- a/app/assets/javascripts/discourse/tests/integration/components/user-menu/bookmark-item-test.js +++ /dev/null @@ -1,84 +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 { hbs } from "ember-cli-htmlbars"; - -function getBookmark(overrides = {}) { - return deepMerge( - { - id: 6, - created_at: "2022-08-05T06:09:39.559Z", - updated_at: "2022-08-05T06:11:27.246Z", - name: "", - reminder_at: "2022-08-05T06:10:42.223Z", - reminder_at_ics_start: "20220805T061042Z", - reminder_at_ics_end: "20220805T071042Z", - pinned: false, - title: "Test poll topic hello world", - fancy_title: "Test poll topic hello world", - excerpt: "poll", - bookmarkable_id: 1009, - bookmarkable_type: "Post", - bookmarkable_url: "http://localhost:4200/t/this-bookmarkable-url/227/1", - tags: [], - tags_descriptions: {}, - truncated: true, - topic_id: 227, - linked_post_number: 1, - deleted: false, - hidden: false, - category_id: 1, - closed: false, - archived: false, - archetype: "regular", - highest_post_number: 45, - last_read_post_number: 31, - bumped_at: "2022-04-21T15:14:37.359Z", - slug: "test-poll-topic-hello-world", - user: { - id: 1, - username: "somebody", - name: "Mr. Somebody", - avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png", - }, - }, - overrides - ); -} - -module("Integration | Component | user-menu | bookmark-item", function (hooks) { - setupRenderingTest(hooks); - - const template = hbs``; - - test("uses bookmarkable_url for the href", async function (assert) { - this.set("bookmark", getBookmark()); - await render(template); - assert.ok( - query("li.bookmark a").href.endsWith("/t/this-bookmarkable-url/227/1") - ); - }); - - test("item label is the bookmarked post author", async function (assert) { - this.set( - "bookmark", - getBookmark({ user: { username: "bookmarkPostAuthor" } }) - ); - await render(template); - assert.strictEqual( - query("li.bookmark .item-label").textContent.trim(), - "bookmarkPostAuthor" - ); - }); - - test("item description is the bookmark title", async function (assert) { - this.set("bookmark", getBookmark({ title: "Custom bookmark title" })); - await render(template); - assert.strictEqual( - query("li.bookmark .item-description").textContent.trim(), - "Custom bookmark title" - ); - }); -}); diff --git a/app/assets/javascripts/discourse/tests/integration/components/user-menu/menu-item-test.js b/app/assets/javascripts/discourse/tests/integration/components/user-menu/menu-item-test.js new file mode 100644 index 00000000000..e1272892ff5 --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/user-menu/menu-item-test.js @@ -0,0 +1,616 @@ +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 { cloneJSON, deepMerge } from "discourse-common/lib/object"; +import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types"; +import Notification from "discourse/models/notification"; +import UserMenuReviewable from "discourse/models/user-menu-reviewable"; +import { hbs } from "ember-cli-htmlbars"; +import { withPluginApi } from "discourse/lib/plugin-api"; +import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item"; +import UserMenuMessageItem from "discourse/lib/user-menu/message-item"; +import UserMenuBookmarkItem from "discourse/lib/user-menu/bookmark-item"; +import UserMenuReviewableItem from "discourse/lib/user-menu/reviewable-item"; +import PrivateMessagesFixture from "discourse/tests/fixtures/private-messages-fixtures"; +import I18n from "I18n"; + +function getNotification(currentUser, siteSettings, site, overrides = {}) { + const notification = Notification.create( + deepMerge( + { + id: 11, + user_id: 1, + notification_type: NOTIFICATION_TYPES.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 !", + display_username: "osama", + original_post_id: 1, + original_post_type: 1, + original_username: "velesin", + }, + }, + overrides + ) + ); + return new UserMenuNotificationItem({ + notification, + currentUser, + siteSettings, + site, + }); +} + +module( + "Integration | Component | user-menu | menu-item | with notification items", + function (hooks) { + setupRenderingTest(hooks); + + const template = hbs``; + + test("pushes `read` to the classList if the notification is read and `unread` if it isn't", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + this.item.notification.read = false; + await render(template); + assert.notOk(exists("li.read")); + assert.ok(exists("li.unread")); + + this.item.notification.read = true; + // await pauseTest(); + await settled(); + + assert.ok( + exists("li.read"), + "the item re-renders when the read property is updated" + ); + assert.notOk( + exists("li.unread"), + "the item re-renders when the read property is updated" + ); + }); + + test("pushes the notification type name to the classList", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + await render(template); + let item = query("li"); + assert.ok(item.classList.contains("mentioned")); + + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + notification_type: NOTIFICATION_TYPES.private_message, + }) + ); + await settled(); + + assert.ok( + exists("li.private-message"), + "replaces underscores in type name with dashes" + ); + }); + + test("pushes is-warning to the classList if the notification originates from a warning PM", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + is_warning: true, + }) + ); + await render(template); + assert.ok(exists("li.is-warning")); + }); + + test("doesn't push is-warning to the classList if the notification doesn't originate from a warning PM", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + await render(template); + assert.ok(!exists("li.is-warning")); + assert.ok(exists("li")); + }); + + test("the item's href links to the topic that the notification originates from", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + await render(template); + const link = query("li a"); + assert.ok(link.href.endsWith("/t/this-is-fancy-title/449/113")); + }); + + test("the item's href links to the group messages if the notification is for a group messages", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + topic_id: null, + post_number: null, + slug: null, + data: { + group_id: 33, + group_name: "grouperss", + username: "ossaama", + }, + }) + ); + await render(template); + const link = query("li a"); + assert.ok(link.href.endsWith("/u/ossaama/messages/grouperss")); + }); + + test("the item's link has a title for accessibility", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + await render(template); + const link = query("li a"); + assert.strictEqual(link.title, I18n.t("notifications.titles.mentioned")); + }); + + test("has elements for label and description", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site) + ); + await render(template); + const label = query("li a .item-label"); + const description = query("li a .item-description"); + + assert.strictEqual( + label.textContent.trim(), + "osama", + "the label's content is the username by default" + ); + + assert.strictEqual( + description.textContent.trim(), + "This is fancy title !", + "the description defaults to the fancy_title" + ); + }); + + test("the description falls back to topic_title from data if fancy_title is absent", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + fancy_title: null, + }) + ); + await render(template); + const description = query("li a .item-description"); + + assert.strictEqual( + description.textContent.trim(), + "this is title before it becomes fancy !", + "topic_title from data is rendered safely" + ); + }); + + test("fancy_title is emoji-unescaped", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + fancy_title: "title with emoji :phone:", + }) + ); + await render(template); + assert.ok( + exists("li a .item-description img.emoji"), + "emojis are unescaped when fancy_title is used for description" + ); + }); + + test("topic_title from data is emoji-unescaped safely", async function (assert) { + this.set( + "item", + getNotification(this.currentUser, this.siteSettings, this.site, { + fancy_title: null, + data: { + topic_title: "unsafe title with unescaped emoji :phone:", + }, + }) + ); + await render(template); + const description = query("li a .item-description"); + + assert.strictEqual( + description.textContent.trim(), + "unsafe title with unescaped emoji", + "topic_title is rendered safely" + ); + assert.ok( + exists(".item-description img.emoji"), + "emoji is rendered correctly" + ); + }); + + test("various aspects can be customized according to the notification's render director", async function (assert) { + withPluginApi("0.1", (api) => { + api.registerNotificationTypeRenderer( + "linked", + (NotificationTypeBase) => { + return class extends NotificationTypeBase { + 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