DEV: Refactor new user menu files (#17879)
This commit includes the changes proposed in #17823. I've made these changes so that plugins that need to add tabs/lists with mixed item types - like the bookmarks tab that displays notifications and bookmarks - to the menu, don't have to write 2 templates like we currently do for the bookmarks/messages tabs (see user-menu/bookmark-notification-item.js that has been deleted in this commit).
This commit is contained in:
parent
b930f4886a
commit
75599fb88e
|
@ -1 +0,0 @@
|
|||
{{component this.component item=@item}}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import templateOnly from "@ember/component/template-only";
|
||||
|
||||
export default templateOnly();
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
import templateOnly from "@ember/component/template-only";
|
||||
|
||||
export default templateOnly();
|
|
@ -5,7 +5,7 @@
|
|||
{{else if this.items.length}}
|
||||
<ul>
|
||||
{{#each this.items as |item|}}
|
||||
{{component this.itemComponent item=item}}
|
||||
<UserMenu::MenuItem @item={{item}}/>
|
||||
{{/each}}
|
||||
</ul>
|
||||
<div class="panel-body-bottom">
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
{{component this.component item=@item}}
|
|
@ -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";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import templateOnly from "@ember/component/template-only";
|
||||
|
||||
export default templateOnly();
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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 <li> 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.
|
||||
*/
|
|
@ -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", {
|
|
@ -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);
|
|
@ -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) {
|
|
@ -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}`;
|
||||
}
|
|
@ -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,
|
|
@ -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);
|
||||
}
|
|
@ -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(
|
|
@ -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", {
|
|
@ -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);
|
||||
}
|
|
@ -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`
|
|
@ -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 });
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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 });
|
||||
}
|
|
@ -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 });
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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");
|
||||
}
|
|
@ -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,
|
|
@ -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() {}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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,
|
|
@ -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";
|
|
@ -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() {
|
||||
|
|
|
@ -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";
|
|
@ -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`<UserMenu::BookmarkItem @item={{this.bookmark}}/>`;
|
||||
|
||||
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"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -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 <a>!",
|
||||
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`<UserMenu::MenuItem @item={{this.item}}/>`;
|
||||
|
||||
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 <a>!",
|
||||
"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 <a>!",
|
||||
"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 <a> unescaped emoji :phone:",
|
||||
},
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
const description = query("li a .item-description");
|
||||
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
"unsafe title with <a> 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 '\"<span>";
|
||||
}
|
||||
|
||||
get icon() {
|
||||
return "wrench";
|
||||
}
|
||||
|
||||
get label() {
|
||||
return "notification label 666 <span>";
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "notification description 123 <script>";
|
||||
}
|
||||
|
||||
get labelClasses() {
|
||||
return ["label-wrapper-1"];
|
||||
}
|
||||
|
||||
get descriptionClasses() {
|
||||
return ["description-class-1"];
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"item",
|
||||
getNotification(this.currentUser, this.siteSettings, this.site, {
|
||||
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 .item-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(".item-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",
|
||||
(NotificationTypeBase) => {
|
||||
return class extends NotificationTypeBase {
|
||||
get description() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get label() {
|
||||
return "notification label";
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"item",
|
||||
getNotification(this.currentUser, this.siteSettings, this.site, {
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
assert.notOk(exists(".item-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",
|
||||
(NotificationTypeBase) => {
|
||||
return class extends NotificationTypeBase {
|
||||
get label() {
|
||||
return null;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return "notification description";
|
||||
}
|
||||
};
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
this.set(
|
||||
"item",
|
||||
getNotification(this.currentUser, this.siteSettings, this.site, {
|
||||
notification_type: NOTIFICATION_TYPES.linked,
|
||||
})
|
||||
);
|
||||
|
||||
await render(template);
|
||||
assert.ok(
|
||||
query("li").textContent.trim(),
|
||||
"notification description",
|
||||
"only notification description is displayed"
|
||||
);
|
||||
assert.notOk(exists(".item-label"), "label is not rendered");
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
function getMessage(overrides = {}) {
|
||||
const message = deepMerge(
|
||||
cloneJSON(
|
||||
PrivateMessagesFixture["/topics/private-messages/eviltrout.json"]
|
||||
.topic_list.topics[0]
|
||||
),
|
||||
overrides
|
||||
);
|
||||
|
||||
return new UserMenuMessageItem({ message });
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | menu-item | with message items",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::MenuItem @item={{this.item}}/>`;
|
||||
|
||||
test("item description is the fancy title of the message", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getMessage({ fancy_title: "This is a <b>safe</b> title!" })
|
||||
);
|
||||
await render(template);
|
||||
assert.strictEqual(
|
||||
query("li.message .item-description").textContent.trim(),
|
||||
"This is a safe title!"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("li.message .item-description b").textContent.trim(),
|
||||
"safe",
|
||||
"fancy title is not escaped"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
function getBookmark(overrides = {}) {
|
||||
const bookmark = 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
|
||||
);
|
||||
|
||||
return new UserMenuBookmarkItem({ bookmark });
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | meun-item | with bookmark items",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::MenuItem @item={{this.item}}/>`;
|
||||
|
||||
test("uses bookmarkable_url for the href", async function (assert) {
|
||||
this.set("item", 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(
|
||||
"item",
|
||||
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("item", getBookmark({ title: "Custom bookmark title" }));
|
||||
await render(template);
|
||||
assert.strictEqual(
|
||||
query("li.bookmark .item-description").textContent.trim(),
|
||||
"Custom bookmark title"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
function getReviewable(currentUser, siteSettings, site, overrides = {}) {
|
||||
const reviewable = UserMenuReviewable.create(
|
||||
Object.assign(
|
||||
{
|
||||
flagger_username: "sayo2",
|
||||
id: 17,
|
||||
pending: false,
|
||||
post_number: 3,
|
||||
topic_fancy_title: "anything hello world",
|
||||
type: "Reviewable",
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
|
||||
return new UserMenuReviewableItem({
|
||||
reviewable,
|
||||
currentUser,
|
||||
siteSettings,
|
||||
site,
|
||||
});
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | menu-item | with reviewable items",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::MenuItem @item={{this.item}}/>`;
|
||||
|
||||
test("doesn't push `reviewed` to the classList if the reviewable is pending", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable(this.currentUser, this.siteSettings, this.site, {
|
||||
pending: true,
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
assert.ok(!exists("li.reviewed"));
|
||||
assert.ok(exists("li"));
|
||||
});
|
||||
|
||||
test("pushes `reviewed` to the classList if the reviewable isn't pending", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable(this.currentUser, this.siteSettings, this.site, {
|
||||
pending: false,
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
assert.ok(exists("li.reviewed"));
|
||||
});
|
||||
|
||||
test("has elements for label and description", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable(this.currentUser, this.siteSettings, this.site)
|
||||
);
|
||||
await render(template);
|
||||
|
||||
const label = query("li .item-label");
|
||||
const description = query("li .item-description");
|
||||
assert.strictEqual(
|
||||
label.textContent.trim(),
|
||||
"sayo2",
|
||||
"the label is the flagger_username"
|
||||
);
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.default_item", {
|
||||
reviewable_id: this.item.reviewable.id,
|
||||
}),
|
||||
"displays the description for the reviewable"
|
||||
);
|
||||
});
|
||||
|
||||
test("the item's label is a placeholder that indicates deleted user if flagger_username is absent", async function (assert) {
|
||||
this.set(
|
||||
"item",
|
||||
getReviewable(this.currentUser, this.siteSettings, this.site, {
|
||||
flagger_username: null,
|
||||
})
|
||||
);
|
||||
await render(template);
|
||||
const label = query("li .item-label");
|
||||
assert.strictEqual(
|
||||
label.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.deleted_user")
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,38 +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 { cloneJSON, deepMerge } from "discourse-common/lib/object";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import PrivateMessagesFixture from "discourse/tests/fixtures/private-messages-fixtures";
|
||||
|
||||
function getMessage(overrides = {}) {
|
||||
const data = cloneJSON(
|
||||
PrivateMessagesFixture["/topics/private-messages/eviltrout.json"].topic_list
|
||||
.topics[0]
|
||||
);
|
||||
return deepMerge(data, overrides);
|
||||
}
|
||||
|
||||
module("Integration | Component | user-menu | message-item", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::MessageItem @item={{this.message}}/>`;
|
||||
|
||||
test("item description is the fancy title of the message", async function (assert) {
|
||||
this.set(
|
||||
"message",
|
||||
getMessage({ fancy_title: "This is a <b>safe</b> title!" })
|
||||
);
|
||||
await render(template);
|
||||
assert.strictEqual(
|
||||
query("li.message .item-description").textContent.trim(),
|
||||
"This is a safe title!"
|
||||
);
|
||||
assert.strictEqual(
|
||||
query("li.message .item-description b").textContent.trim(),
|
||||
"safe",
|
||||
"fancy title is not escaped"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -1,397 +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 { 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 = {}) {
|
||||
return 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 <a>!",
|
||||
display_username: "osama",
|
||||
original_post_id: 1,
|
||||
original_post_type: 1,
|
||||
original_username: "velesin",
|
||||
},
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | notification-item",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
const template = hbs`<UserMenu::NotificationItem @item={{this.notification}}/>`;
|
||||
|
||||
test("pushes `read` to the classList if the notification is read and `unread` if it isn't", async function (assert) {
|
||||
this.set("notification", getNotification());
|
||||
this.notification.read = false;
|
||||
await render(template);
|
||||
assert.notOk(exists("li.read"));
|
||||
assert.ok(exists("li.unread"));
|
||||
|
||||
this.notification.read = true;
|
||||
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("notification", getNotification());
|
||||
await render(template);
|
||||
let item = query("li");
|
||||
assert.ok(item.classList.contains("mentioned"));
|
||||
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
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("notification", getNotification({ 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("notification", getNotification());
|
||||
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("notification", getNotification());
|
||||
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(
|
||||
"notification",
|
||||
getNotification({
|
||||
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("notification", getNotification());
|
||||
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("notification", getNotification());
|
||||
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 <a>!",
|
||||
"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(
|
||||
"notification",
|
||||
getNotification({
|
||||
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 <a>!",
|
||||
"topic_title from data is rendered safely"
|
||||
);
|
||||
});
|
||||
|
||||
test("fancy_title is emoji-unescaped", async function (assert) {
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
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 not emoji-unescaped", async function (assert) {
|
||||
this.set(
|
||||
"notification",
|
||||
getNotification({
|
||||
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 :phone:",
|
||||
"emojis aren't unescaped when topic title is not safe"
|
||||
);
|
||||
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 labelClasses() {
|
||||
return ["label-wrapper-1"];
|
||||
}
|
||||
|
||||
get descriptionClasses() {
|
||||
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 .item-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(".item-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(".item-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(".item-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);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,75 +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,
|
||||
post_number: 3,
|
||||
topic_fancy_title: "anything hello world",
|
||||
type: "Reviewable",
|
||||
},
|
||||
overrides
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
module(
|
||||
"Integration | Component | user-menu | reviewable-item",
|
||||
function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
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 }));
|
||||
await render(template);
|
||||
assert.ok(!exists("li.reviewed"));
|
||||
assert.ok(exists("li"));
|
||||
});
|
||||
|
||||
test("pushes `reviewed` to the classList if the reviewable isn't pending", async function (assert) {
|
||||
this.set("item", getReviewable({ pending: false }));
|
||||
await render(template);
|
||||
assert.ok(exists("li.reviewed"));
|
||||
});
|
||||
|
||||
test("has elements for label and description", async function (assert) {
|
||||
this.set("item", getReviewable());
|
||||
await render(template);
|
||||
|
||||
const label = query("li .item-label");
|
||||
const description = query("li .item-description");
|
||||
assert.strictEqual(
|
||||
label.textContent.trim(),
|
||||
"sayo2",
|
||||
"the label is the flagger_username"
|
||||
);
|
||||
assert.strictEqual(
|
||||
description.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.default_item", {
|
||||
reviewable_id: this.item.id,
|
||||
}),
|
||||
"displays the description for the reviewable"
|
||||
);
|
||||
});
|
||||
|
||||
test("the item's label is a placeholder that indicates deleted user if flagger_username is absent", async function (assert) {
|
||||
this.set("item", getReviewable({ flagger_username: null }));
|
||||
await render(template);
|
||||
const label = query("li .item-label");
|
||||
assert.strictEqual(
|
||||
label.textContent.trim(),
|
||||
I18n.t("user_menu.reviewable.deleted_user")
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import Notification from "discourse/models/notification";
|
||||
import I18n from "I18n";
|
||||
|
@ -33,7 +33,7 @@ function getNotification(overrides = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | bookmark-reminder", function () {
|
||||
discourseModule("Unit | Notification Types | bookmark-reminder", function () {
|
||||
test("linkTitle", function (assert) {
|
||||
const notification = getNotification({
|
||||
data: { bookmark_name: "My awesome bookmark" },
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
import I18n from "I18n";
|
||||
|
||||
|
@ -28,7 +28,7 @@ function getNotification(overrides = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | granted-badge", function () {
|
||||
discourseModule("Unit | Notification Types | granted-badge", function () {
|
||||
test("linkHref", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
|
||||
function getNotification(overrides = {}) {
|
||||
|
@ -34,7 +34,7 @@ function getNotification(overrides = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | group-mentioned", function () {
|
||||
discourseModule("Unit | Notification Types | group-mentioned", function () {
|
||||
test("label", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
import I18n from "I18n";
|
||||
|
||||
|
@ -29,7 +29,7 @@ function getNotification(overrides = {}) {
|
|||
}
|
||||
|
||||
discourseModule(
|
||||
"Unit | Notification Items | group-message-summary",
|
||||
"Unit | Notification Types | group-message-summary",
|
||||
function () {
|
||||
test("description", function (assert) {
|
||||
const notification = getNotification();
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
import I18n from "I18n";
|
||||
|
||||
|
@ -31,7 +31,7 @@ function getNotification(overrides = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | liked-consolidated", function () {
|
||||
discourseModule("Unit | Notification Types | liked-consolidated", function () {
|
||||
test("linkHref", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
|
@ -2,7 +2,7 @@ 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 { createRenderDirector } from "discourse/tests/helpers/notification-types-helper";
|
||||
import Notification from "discourse/models/notification";
|
||||
import I18n from "I18n";
|
||||
|
||||
|
@ -33,7 +33,7 @@ function getNotification(overrides = {}) {
|
|||
);
|
||||
}
|
||||
|
||||
discourseModule("Unit | Notification Items | liked", function () {
|
||||
discourseModule("Unit | Notification Types | liked", function () {
|
||||
test("label", function (assert) {
|
||||
const notification = getNotification();
|
||||
const director = createRenderDirector(
|
|
@ -1,6 +1,6 @@
|
|||
import { discourseModule } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/reviewable-items-helper";
|
||||
import { createRenderDirector } from "discourse/tests/helpers/reviewable-types-helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { emojiUnescape } from "discourse/lib/text";
|
||||
import UserMenuReviewable from "discourse/models/user-menu-reviewable";
|
Loading…
Reference in New Issue