DEV: Refactor notification/reviewable items rendering in the new user menu (#17792)
Prior to this commit, we had a default Glimmer component that was responsible for handling generic rendering of notifications in the user menu, and many notification types had a custom Glimmer component that inherited from the default component to customize how they were rendered. That implementation was less than ideal because it meant plugins would have to create Glimmer components to customize notification types added by them and that would make the surface area of the API too big. This commit changes the implementation so there's only one Glimmer component for rendering notifications, and then notification types that need to be customized can create a regular JavaScript class - `renderDirector` in the code - that provides the Glimmer component with the content it should display. We also introduce an API for plugins to register a renderer for a notification type or override an existing one. Some of the changes are partially extracted from https://github.com/discourse/discourse/pull/17379.
This commit is contained in:
parent
d600c36036
commit
0df1c4eab2
|
@ -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";
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
{{else if this.items.length}}
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
{{component item.userMenuComponent item=item}}
|
||||
{{component this.itemComponent item=item}}
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class="panel-body-bottom">
|
||||
|
|
|
@ -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;
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
}
|
|
@ -7,17 +7,13 @@
|
|||
{{d-icon this.icon}}
|
||||
<div>
|
||||
{{#if this.label}}
|
||||
{{#if this.wrapLabel}}
|
||||
<span class={{concat "notification-label " this.labelWrapperClasses}}>
|
||||
{{this.label}}
|
||||
</span>
|
||||
{{else}}
|
||||
<span>{{this.label}}</span>
|
||||
{{/if}}
|
||||
<span class={{concat "notification-label " this.labelWrapperClasses}}>
|
||||
{{this.label}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{#if this.description}}
|
||||
<span
|
||||
class={{concat "notification-description " this.descriptionElementClasses}}
|
||||
class={{concat "notification-description " this.descriptionWrapperClasses}}
|
||||
data-topic-id={{this.topicId}}
|
||||
>
|
||||
{{this.description}}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,10 @@ export default class UserMenuNotificationsList extends UserMenuItemsList {
|
|||
}
|
||||
}
|
||||
|
||||
get itemComponent() {
|
||||
return "user-menu/notification-item";
|
||||
}
|
||||
|
||||
fetchItems() {
|
||||
const params = {
|
||||
limit: 30,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) => {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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 <li> 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 <span>.
|
||||
*/
|
||||
get labelWrapperClasses() {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {string[]} Include additional classes to the description's wrapper <span>.
|
||||
*/
|
||||
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];
|
||||
}
|
||||
}
|
|
@ -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", {
|
|
@ -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);
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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"];
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -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() {
|
|
@ -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;
|
|
@ -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");
|
||||
}
|
|
@ -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,
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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 <a>!",
|
||||
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`<UserMenu::BookmarkReminderNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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 <a>!",
|
||||
bookmarkable_url: "/chat/channel/33",
|
||||
},
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
const description = query("li .notification-description");
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
"this is unsafe bookmark title <a>!",
|
||||
"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 <a>!",
|
||||
"fancy_title is safe and rendered correctly"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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 <a>",
|
||||
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`<UserMenu::GrantedBadgeNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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 <a>",
|
||||
}),
|
||||
"label is rendered safely"
|
||||
);
|
||||
assert.ok(!exists("li .notification-label"));
|
||||
assert.ok(!exists("li .notification-description"));
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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 <a>!",
|
||||
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`<UserMenu::GroupMentionedNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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 <a>!"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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`<UserMenu::GroupMessageSummaryNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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"));
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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`<UserMenu::LikedConsolidatedNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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 })
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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 <a>!",
|
||||
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`<UserMenu::LikedNotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
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 <a>!",
|
||||
"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 <a>!",
|
||||
"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 <a>!",
|
||||
"the description displays the topic title"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -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 <img> 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 '\"<span>";
|
||||
}
|
||||
|
||||
get icon() {
|
||||
return "wrench";
|
||||
}
|
||||
|
||||
get label() {
|
||||
return "notification label 666 <span>";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "notification description 123 <script>";
|
||||
}
|
||||
|
||||
get labelWrapperClasses() {
|
||||
return ["label-wrapper-1"];
|
||||
}
|
||||
|
||||
get descriptionWrapperClasses() {
|
||||
return ["description-class-1"];
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
|
||||
assert.ok(
|
||||
exists("li.additional.classes"),
|
||||
"extra classes are included on the item"
|
||||
);
|
||||
|
||||
const link = query("li a");
|
||||
assert.ok(
|
||||
link.href.endsWith("/somewhere/awesome"),
|
||||
"link href is customized"
|
||||
);
|
||||
assert.strictEqual(
|
||||
link.title,
|
||||
"hello world this is unsafe '\"<span>",
|
||||
"link title is customized and rendered safely"
|
||||
);
|
||||
|
||||
assert.ok(exists("svg.d-icon-wrench"), "icon is customized");
|
||||
|
||||
const label = query("li .notification-label");
|
||||
assert.ok(
|
||||
label.classList.contains("label-wrapper-1"),
|
||||
"label wrapper has additional classes"
|
||||
);
|
||||
assert.strictEqual(
|
||||
label.textContent.trim(),
|
||||
"notification label 666 <span>",
|
||||
"label content is customized"
|
||||
);
|
||||
|
||||
const description = query(".notification-description");
|
||||
assert.ok(
|
||||
description.classList.contains("description-class-1"),
|
||||
"description has additional classes"
|
||||
);
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
"notification description 123 <script>",
|
||||
"description content is customized"
|
||||
);
|
||||
});
|
||||
|
||||
test("description can be omitted", async function (assert) {
|
||||
withPluginApi("0.1", (api) => {
|
||||
api.registerNotificationTypeRenderer(
|
||||
"linked",
|
||||
(NotificationItemBase) => {
|
||||
return class extends NotificationItemBase {
|
||||
get description() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get label() {
|
||||
return "notification label";
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
assert.notOk(
|
||||
exists(".notification-description"),
|
||||
"description is not rendered"
|
||||
);
|
||||
assert.ok(
|
||||
query("li").textContent.trim(),
|
||||
"notification label",
|
||||
"only label content is displayed"
|
||||
);
|
||||
});
|
||||
|
||||
test("label can be omitted", async function (assert) {
|
||||
withPluginApi("0.1", (api) => {
|
||||
api.registerNotificationTypeRenderer(
|
||||
"linked",
|
||||
(NotificationItemBase) => {
|
||||
return class extends NotificationItemBase {
|
||||
get label() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "notification description";
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
assert.ok(
|
||||
query("li").textContent.trim(),
|
||||
"notification description",
|
||||
"only notification description is displayed"
|
||||
);
|
||||
assert.notOk(exists(".notification-label"), "label is not rendered");
|
||||
});
|
||||
|
||||
test("custom click handlers", async function (assert) {
|
||||
let klass;
|
||||
withPluginApi("0.1", (api) => {
|
||||
api.registerNotificationTypeRenderer(
|
||||
"linked",
|
||||
(NotificationItemBase) => {
|
||||
klass = class extends NotificationItemBase {
|
||||
static onClickCalled = false;
|
||||
|
||||
get linkHref() {
|
||||
return "#";
|
||||
}
|
||||
|
||||
onClick() {
|
||||
klass.onClickCalled = true;
|
||||
}
|
||||
};
|
||||
return klass;
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
await click("li a");
|
||||
assert.ok(klass.onClickCalled);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ function getReviewable(overrides = {}) {
|
|||
pending: false,
|
||||
post_number: 3,
|
||||
topic_fancy_title: "anything hello world",
|
||||
type: "ReviewableFlaggedPost",
|
||||
type: "Reviewable",
|
||||
},
|
||||
overrides
|
||||
)
|
||||
|
@ -23,11 +23,11 @@ function getReviewable(overrides = {}) {
|
|||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | default-reviewable-item",
|
||||
"Integration | Component | user-menu | reviewable-item",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::DefaultReviewableItem @item={{this.item}}/>`;
|
||||
const template = hbs`<UserMenu::ReviewableItem @item={{this.item}}/>`;
|
||||
|
||||
test("doesn't push `reviewed` to the classList if the reviewable is pending", async function (assert) {
|
||||
this.set("item", getReviewable({ pending: true }));
|
|
@ -1,77 +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 UserMenuReviewable from "discourse/models/user-menu-reviewable";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import I18n from "I18n";
|
||||
|
||||
function getReviewable(overrides = {}) {
|
||||
return UserMenuReviewable.create(
|
||||
Object.assign(
|
||||
{
|
||||
flagger_username: "sayo2",
|
||||
id: 17,
|
||||
pending: false,
|
||||
topic_fancy_title: "anything hello world",
|
||||
type: "ReviewableQueuedPost",
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | reviewable-queued-post-item",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::ReviewableQueuedPostItem @item={{this.item}}/>`;
|
||||
|
||||
test("doesn't escape topic_fancy_title because it's safe", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable({
|
||||
topic_fancy_title: "This is safe title <a> :heart:",
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
const description = query(".reviewable-description");
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.new_post_in_topic", {
|
||||
title: "This is safe title <a>",
|
||||
})
|
||||
);
|
||||
assert.strictEqual(
|
||||
description.querySelectorAll("img.emoji").length,
|
||||
1,
|
||||
"emojis are rendered"
|
||||
);
|
||||
});
|
||||
|
||||
test("escapes payload_title because it's not safe", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable({
|
||||
topic_fancy_title: null,
|
||||
payload_title: "This is unsafe title <a> :heart:",
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
const description = query(".reviewable-description");
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.new_post_in_topic", {
|
||||
title: "This is unsafe title <a>",
|
||||
})
|
||||
);
|
||||
assert.strictEqual(
|
||||
description.querySelectorAll("img.emoji").length,
|
||||
1,
|
||||
"emojis are rendered"
|
||||
);
|
||||
assert.ok(!exists(".reviewable-description a"));
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,85 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import Notification from "discourse/models/notification";
|
||||
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 <a>!",
|
||||
display_username: "osama",
|
||||
bookmark_name: null,
|
||||
bookmarkable_url: "/t/sometopic/3232",
|
||||
},
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | bookmark-reminder", function () {
|
||||
test("linkTitle", function (assert) {
|
||||
const notification = getNotification({
|
||||
data: { bookmark_name: "My awesome bookmark" },
|
||||
});
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"bookmark_reminder",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.linkTitle,
|
||||
I18n.t("notifications.titles.bookmark_reminder_with_name", {
|
||||
name: "My awesome bookmark",
|
||||
}),
|
||||
"content includes the bookmark name when the bookmark has a name"
|
||||
);
|
||||
|
||||
delete notification.data.bookmark_name;
|
||||
assert.strictEqual(
|
||||
director.linkTitle,
|
||||
"bookmark reminder",
|
||||
"derived from the notification name when there's no bookmark name"
|
||||
);
|
||||
});
|
||||
|
||||
test("description", function (assert) {
|
||||
const notification = getNotification({
|
||||
fancy_title: "my fancy title!",
|
||||
data: { topic_title: null, title: "custom bookmark title" },
|
||||
});
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"bookmark_reminder",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.deepEqual(
|
||||
director.description,
|
||||
htmlSafe("my fancy title!"),
|
||||
"description is the fancy title by default"
|
||||
);
|
||||
|
||||
delete notification.fancy_title;
|
||||
assert.strictEqual(
|
||||
director.description,
|
||||
"custom bookmark title",
|
||||
"description falls back to the bookmark title if there's no fancy title"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,59 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
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: 44,
|
||||
badge_slug: "badge-15-slug",
|
||||
badge_name: "Badge 15",
|
||||
username: "gg.player",
|
||||
},
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | granted-badge", function () {
|
||||
test("linkHref", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"granted_badge",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.linkHref,
|
||||
"/badges/44/badge-15-slug?username=gg.player",
|
||||
"links to the badge page and filters by the username"
|
||||
);
|
||||
});
|
||||
|
||||
test("description", async function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"granted_badge",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.description,
|
||||
I18n.t("notifications.granted_badge", { description: "Badge 15" }),
|
||||
"contains the right content"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
|
||||
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 <a>!",
|
||||
original_post_id: 112,
|
||||
original_post_type: 1,
|
||||
original_username: "kolary",
|
||||
display_username: "osama",
|
||||
group_id: 333,
|
||||
group_name: "hikers",
|
||||
},
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | group-mentioned", function () {
|
||||
test("label", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"group_mentioned",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.label,
|
||||
"osama @hikers",
|
||||
"contains the user who mentioned and the mentioned group"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,51 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule(
|
||||
"Unit | Notification Items | group-message-summary",
|
||||
function () {
|
||||
test("description", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"group_message_summary",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.description,
|
||||
I18n.t("notifications.group_message_summary", {
|
||||
group_name: "drummers",
|
||||
count: 13,
|
||||
}),
|
||||
"displays the right content"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -0,0 +1,62 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | liked-consolidated", function () {
|
||||
test("linkHref", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"liked_consolidated",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.linkHref,
|
||||
"/u/eviltrout/notifications/likes-received?acting_username=liker439",
|
||||
"links to the likes received page of the user"
|
||||
);
|
||||
});
|
||||
|
||||
test("description", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"liked_consolidated",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.strictEqual(
|
||||
director.description,
|
||||
I18n.t("notifications.liked_consolidated_description", { count: 44 }),
|
||||
"displays the right content"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,73 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { NOTIFICATION_TYPES } from "discourse/tests/fixtures/concerns/notification-types";
|
||||
import { deepMerge } from "discourse-common/lib/object";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/notification-items-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
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 <a>!",
|
||||
username: "osama",
|
||||
display_username: "osama",
|
||||
username2: "shrek",
|
||||
count: 2,
|
||||
},
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | liked", function () {
|
||||
test("label", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
||||
notification,
|
||||
"liked",
|
||||
this.siteSettings
|
||||
);
|
||||
notification.data.count = 2;
|
||||
assert.strictEqual(
|
||||
director.label,
|
||||
I18n.t("notifications.liked_by_2_users", {
|
||||
username: "osama",
|
||||
username2: "shrek",
|
||||
}),
|
||||
"concatenates both usernames with comma when count is 2"
|
||||
);
|
||||
|
||||
notification.data.count = 3;
|
||||
assert.strictEqual(
|
||||
director.label,
|
||||
I18n.t("notifications.liked_by_multiple_users", {
|
||||
username: "osama",
|
||||
username2: "shrek",
|
||||
count: 1,
|
||||
}),
|
||||
"concatenates 2 usernames with comma and displays the remaining count when count larger than 2"
|
||||
);
|
||||
|
||||
notification.data.count = 1;
|
||||
delete notification.data.username2;
|
||||
assert.strictEqual(
|
||||
director.label,
|
||||
"osama",
|
||||
"displays the liker's username when the count is 1"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,56 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/reviewable-items-helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import UserMenuReviewable from "discourse/models/user-menu-reviewable";
|
||||
import I18n from "I18n";
|
||||
|
||||
function getReviewable(overrides = {}) {
|
||||
return UserMenuReviewable.create(
|
||||
Object.assign(
|
||||
{
|
||||
flagger_username: "sayo2",
|
||||
id: 17,
|
||||
pending: false,
|
||||
topic_fancy_title: "anything hello world",
|
||||
type: "ReviewableQueuedPost",
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Reviewable Items | queued-post", function () {
|
||||
test("description", function (assert) {
|
||||
const reviewable = getReviewable({
|
||||
topic_fancy_title: "This is safe title <a> :heart:",
|
||||
});
|
||||
const director = createRenderDirector(
|
||||
reviewable,
|
||||
"ReviewableQueuedPost",
|
||||
this.siteSettings
|
||||
);
|
||||
assert.deepEqual(
|
||||
director.description,
|
||||
htmlSafe(
|
||||
I18n.t("user_menu.reviewable.new_post_in_topic", {
|
||||
title: `This is safe title <a> ${emojiUnescape(":heart:")}`,
|
||||
})
|
||||
),
|
||||
"contains the fancy title without escaping because it's already safe"
|
||||
);
|
||||
|
||||
delete reviewable.topic_fancy_title;
|
||||
reviewable.payload_title = "This is unsafe title <a> :heart:";
|
||||
assert.deepEqual(
|
||||
director.description,
|
||||
htmlSafe(
|
||||
I18n.t("user_menu.reviewable.new_post_in_topic", {
|
||||
title: `This is unsafe title <a> ${emojiUnescape(":heart:")}`,
|
||||
})
|
||||
),
|
||||
"contains the payload title escaped and correctly unescapes emojis"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -151,6 +151,21 @@
|
|||
.multi-user {
|
||||
white-space: unset;
|
||||
}
|
||||
|
||||
.notification-label {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove when the widgets-based implementation of the user menu is removed
|
||||
.user-menu:not(.revamped) {
|
||||
.quick-access-panel {
|
||||
li {
|
||||
span:first-child {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -312,10 +327,6 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
span.double-user,
|
||||
// e.g., "username, username2"
|
||||
span.multi-user
|
||||
|
|
Loading…
Reference in New Issue