FEATURE: Site setting to display user avatars in user menu (#24514)
This commit is contained in:
parent
e4c373194d
commit
ee05f57e2d
|
@ -73,7 +73,11 @@ export default class UserMenuBookmarksList extends UserMenuNotificationsList {
|
||||||
await Bookmark.applyTransformations(bookmarks);
|
await Bookmark.applyTransformations(bookmarks);
|
||||||
content.push(
|
content.push(
|
||||||
...bookmarks.map((bookmark) => {
|
...bookmarks.map((bookmark) => {
|
||||||
return new UserMenuBookmarkItem({ bookmark });
|
return new UserMenuBookmarkItem({
|
||||||
|
bookmark,
|
||||||
|
siteSettings: this.siteSettings,
|
||||||
|
site: this.site,
|
||||||
|
});
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import avatar from "discourse/helpers/bound-avatar-template";
|
||||||
|
import dIcon from "discourse-common/helpers/d-icon";
|
||||||
|
|
||||||
|
export default class IconAvatar extends Component {
|
||||||
|
<template>
|
||||||
|
<div class="icon-avatar">
|
||||||
|
{{avatar @data.avatarTemplate "small"}}
|
||||||
|
{{dIcon @data.icon}}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -4,7 +4,11 @@
|
||||||
title={{this.linkTitle}}
|
title={{this.linkTitle}}
|
||||||
{{on "click" this.onClick}}
|
{{on "click" this.onClick}}
|
||||||
>
|
>
|
||||||
{{d-icon this.icon}}
|
{{#if this.iconComponent}}
|
||||||
|
<this.iconComponent @data={{this.iconComponentArgs}} />
|
||||||
|
{{else}}
|
||||||
|
{{d-icon this.icon}}
|
||||||
|
{{/if}}
|
||||||
<div>
|
<div>
|
||||||
{{#if this.label}}
|
{{#if this.label}}
|
||||||
<span class={{concat "item-label " this.labelClass}}>
|
<span class={{concat "item-label " this.labelClass}}>
|
||||||
|
|
|
@ -49,6 +49,14 @@ export default class UserMenuItem extends Component {
|
||||||
return this.#item.topicId;
|
return this.#item.topicId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get iconComponent() {
|
||||||
|
return this.#item.iconComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get iconComponentArgs() {
|
||||||
|
return this.#item.iconComponentArgs;
|
||||||
|
}
|
||||||
|
|
||||||
get #item() {
|
get #item() {
|
||||||
return this.args.item;
|
return this.args.item;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<div
|
<div
|
||||||
class="user-menu revamped menu-panel drop-down"
|
class={{this.classNames}}
|
||||||
data-max-width="320"
|
data-max-width="320"
|
||||||
{{did-insert this.triggerRenderedAppEvent}}
|
{{did-insert this.triggerRenderedAppEvent}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -196,6 +196,14 @@ export default class UserMenu extends Component {
|
||||||
@tracked currentPanelComponent = DEFAULT_PANEL_COMPONENT;
|
@tracked currentPanelComponent = DEFAULT_PANEL_COMPONENT;
|
||||||
@tracked currentNotificationTypes;
|
@tracked currentNotificationTypes;
|
||||||
|
|
||||||
|
get classNames() {
|
||||||
|
let classes = ["user-menu", "revamped", "menu-panel", "drop-down"];
|
||||||
|
if (this.siteSettings.show_user_menu_avatars) {
|
||||||
|
classes.push("show-avatars");
|
||||||
|
}
|
||||||
|
return classes.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get topTabs() {
|
get topTabs() {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
|
|
|
@ -77,6 +77,16 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
||||||
const topics = data.topics.map((t) => this.store.createRecord("topic", t));
|
const topics = data.topics.map((t) => this.store.createRecord("topic", t));
|
||||||
await Topic.applyTransformations(topics);
|
await Topic.applyTransformations(topics);
|
||||||
|
|
||||||
|
if (this.siteSettings.show_user_menu_avatars) {
|
||||||
|
// Populate avatar_template for lastPoster
|
||||||
|
const usersById = new Map(data.users.map((u) => [u.id, u]));
|
||||||
|
topics.forEach((t) => {
|
||||||
|
t.last_poster_avatar_template = usersById.get(
|
||||||
|
t.lastPoster.user_id
|
||||||
|
).avatar_template;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const readNotifications = await Notification.initializeNotifications(
|
const readNotifications = await Notification.initializeNotifications(
|
||||||
data.read_notifications
|
data.read_notifications
|
||||||
);
|
);
|
||||||
|
@ -96,7 +106,13 @@ export default class UserMenuMessagesList extends UserMenuNotificationsList {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
content.push(new UserMenuMessageItem({ message: item }));
|
content.push(
|
||||||
|
new UserMenuMessageItem({
|
||||||
|
message: item,
|
||||||
|
siteSettings: this.siteSettings,
|
||||||
|
site: this.site,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
|
import UserMenuIconAvatar from "discourse/components/user-menu/icon-avatar";
|
||||||
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
|
|
||||||
export default class UserMenuBaseItem {
|
export default class UserMenuBaseItem {
|
||||||
|
constructor({ siteSettings, site }) {
|
||||||
|
this.site = site;
|
||||||
|
this.siteSettings = siteSettings;
|
||||||
|
}
|
||||||
get className() {}
|
get className() {}
|
||||||
|
|
||||||
get linkHref() {
|
get linkHref() {
|
||||||
|
@ -30,6 +35,20 @@ export default class UserMenuBaseItem {
|
||||||
|
|
||||||
get topicId() {}
|
get topicId() {}
|
||||||
|
|
||||||
|
get avatarTemplate() {}
|
||||||
|
|
||||||
|
get iconComponent() {
|
||||||
|
return this.siteSettings.show_user_menu_avatars ? UserMenuIconAvatar : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get iconComponentArgs() {
|
||||||
|
return {
|
||||||
|
avatarTemplate:
|
||||||
|
this.avatarTemplate || this.site.system_user_avatar_template,
|
||||||
|
icon: this.icon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
onClick({ event, closeUserMenu }) {
|
onClick({ event, closeUserMenu }) {
|
||||||
if (wantsNewWindow(event)) {
|
if (wantsNewWindow(event)) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -34,4 +34,8 @@ export default class UserMenuBookmarkItem extends UserMenuBaseItem {
|
||||||
get topicId() {
|
get topicId() {
|
||||||
return this.bookmark.topic_id;
|
return this.bookmark.topic_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get avatarTemplate() {
|
||||||
|
return this.bookmark.user.avatar_template;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,4 +41,8 @@ export default class UserMenuMessageItem extends UserMenuBaseItem {
|
||||||
get topicId() {
|
get topicId() {
|
||||||
return this.message.id;
|
return this.message.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get avatarTemplate() {
|
||||||
|
return this.message.last_poster_avatar_template;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,10 @@ export default class UserMenuNotificationItem extends UserMenuBaseItem {
|
||||||
return this.notification.topic_id;
|
return this.notification.topic_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get avatarTemplate() {
|
||||||
|
return this.notification.acting_user_avatar_template;
|
||||||
|
}
|
||||||
|
|
||||||
get #notificationName() {
|
get #notificationName() {
|
||||||
return this.site.notificationLookup[this.notification.notification_type];
|
return this.site.notificationLookup[this.notification.notification_type];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1101,3 +1101,36 @@ acceptance("User menu - Dismiss button", function (needs) {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
acceptance("User menu - avatars", function (needs) {
|
||||||
|
needs.user();
|
||||||
|
|
||||||
|
needs.settings({
|
||||||
|
show_user_menu_avatars: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
test("It shows user avatars for various notifications on all notifications pane", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
assert.ok(exists("li.notification.edited .icon-avatar"));
|
||||||
|
assert.ok(exists("li.notification.replied .icon-avatar"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("It shows user avatars for messages", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-messages");
|
||||||
|
|
||||||
|
assert.ok(exists("li.notification.private-message .icon-avatar"));
|
||||||
|
assert.ok(exists("li.message .icon-avatar"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("It shows user avatars for bookmark items and bookmark reminder notification items", async function (assert) {
|
||||||
|
await visit("/");
|
||||||
|
await click(".d-header-icons .current-user");
|
||||||
|
await click("#user-menu-button-bookmarks");
|
||||||
|
|
||||||
|
assert.ok(exists("li.notification.bookmark-reminder .icon-avatar"));
|
||||||
|
assert.ok(exists("li.bookmark .icon-avatar"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -6,6 +6,7 @@ export default {
|
||||||
{
|
{
|
||||||
id: 5340,
|
id: 5340,
|
||||||
notification_type: NOTIFICATION_TYPES.edited,
|
notification_type: NOTIFICATION_TYPES.edited,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
post_number: 1,
|
post_number: 1,
|
||||||
topic_id: 130,
|
topic_id: 130,
|
||||||
|
@ -23,6 +24,7 @@ export default {
|
||||||
{
|
{
|
||||||
id: 123,
|
id: 123,
|
||||||
notification_type: NOTIFICATION_TYPES.replied,
|
notification_type: NOTIFICATION_TYPES.replied,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
post_number: 1,
|
post_number: 1,
|
||||||
topic_id: 1234,
|
topic_id: 1234,
|
||||||
|
@ -33,12 +35,14 @@ export default {
|
||||||
{
|
{
|
||||||
id: 456,
|
id: 456,
|
||||||
notification_type: NOTIFICATION_TYPES.liked_consolidated,
|
notification_type: NOTIFICATION_TYPES.liked_consolidated,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
data: { display_username: "aquaman", count: "5" },
|
data: { display_username: "aquaman", count: "5" },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 789,
|
id: 789,
|
||||||
notification_type: NOTIFICATION_TYPES.group_message_summary,
|
notification_type: NOTIFICATION_TYPES.group_message_summary,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
post_number: null,
|
post_number: null,
|
||||||
topic_id: null,
|
topic_id: null,
|
||||||
|
@ -53,6 +57,7 @@ export default {
|
||||||
{
|
{
|
||||||
id: 1234,
|
id: 1234,
|
||||||
notification_type: NOTIFICATION_TYPES.invitee_accepted,
|
notification_type: NOTIFICATION_TYPES.invitee_accepted,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
post_number: null,
|
post_number: null,
|
||||||
topic_id: null,
|
topic_id: null,
|
||||||
|
@ -62,6 +67,7 @@ export default {
|
||||||
{
|
{
|
||||||
id: 5678,
|
id: 5678,
|
||||||
notification_type: NOTIFICATION_TYPES.membership_request_accepted,
|
notification_type: NOTIFICATION_TYPES.membership_request_accepted,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
post_number: null,
|
post_number: null,
|
||||||
topic_id: null,
|
topic_id: null,
|
||||||
|
|
|
@ -5,6 +5,7 @@ export default {
|
||||||
id: 1713,
|
id: 1713,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
notification_type: 24,
|
notification_type: 24,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
high_priority: true,
|
high_priority: true,
|
||||||
created_at: "2022-08-05T17:27:24.873Z",
|
created_at: "2022-08-05T17:27:24.873Z",
|
||||||
|
@ -69,6 +70,7 @@ export default {
|
||||||
id: 8315,
|
id: 8315,
|
||||||
user_id: 1,
|
user_id: 1,
|
||||||
notification_type: 6,
|
notification_type: 6,
|
||||||
|
acting_user_avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png",
|
||||||
read: false,
|
read: false,
|
||||||
high_priority: true,
|
high_priority: true,
|
||||||
created_at: "2022-08-05T17:27:24.873Z",
|
created_at: "2022-08-05T17:27:24.873Z",
|
||||||
|
@ -139,5 +141,9 @@ export default {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
read_notifications: [],
|
read_notifications: [],
|
||||||
|
users: [{id: 13,
|
||||||
|
username: "mixtape",
|
||||||
|
avatar_template: "/letter_avatar_proxy/v4/letter/o/f05b48/{size}.png"
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -400,7 +400,7 @@ module(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function getMessage(overrides = {}) {
|
function getMessage(overrides = {}, siteSettings, site) {
|
||||||
const message = deepMerge(
|
const message = deepMerge(
|
||||||
cloneJSON(
|
cloneJSON(
|
||||||
PrivateMessagesFixture["/topics/private-messages/eviltrout.json"]
|
PrivateMessagesFixture["/topics/private-messages/eviltrout.json"]
|
||||||
|
@ -409,7 +409,7 @@ function getMessage(overrides = {}) {
|
||||||
overrides
|
overrides
|
||||||
);
|
);
|
||||||
|
|
||||||
return new UserMenuMessageItem({ message });
|
return new UserMenuMessageItem({ message, siteSettings, site });
|
||||||
}
|
}
|
||||||
|
|
||||||
module(
|
module(
|
||||||
|
@ -422,7 +422,11 @@ module(
|
||||||
test("item description is the fancy title of the message", async function (assert) {
|
test("item description is the fancy title of the message", async function (assert) {
|
||||||
this.set(
|
this.set(
|
||||||
"item",
|
"item",
|
||||||
getMessage({ fancy_title: "This is a <b>safe</b> title!" })
|
getMessage(
|
||||||
|
{ fancy_title: "This is a <b>safe</b> title!" },
|
||||||
|
this.siteSettings,
|
||||||
|
this.site
|
||||||
|
)
|
||||||
);
|
);
|
||||||
await render(template);
|
await render(template);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
@ -438,7 +442,7 @@ module(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
function getBookmark(overrides = {}) {
|
function getBookmark(overrides = {}, siteSettings, site) {
|
||||||
const bookmark = deepMerge(
|
const bookmark = deepMerge(
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
|
@ -480,7 +484,7 @@ function getBookmark(overrides = {}) {
|
||||||
overrides
|
overrides
|
||||||
);
|
);
|
||||||
|
|
||||||
return new UserMenuBookmarkItem({ bookmark });
|
return new UserMenuBookmarkItem({ bookmark, siteSettings, site });
|
||||||
}
|
}
|
||||||
|
|
||||||
module(
|
module(
|
||||||
|
@ -491,7 +495,7 @@ module(
|
||||||
const template = hbs`<UserMenu::MenuItem @item={{this.item}}/>`;
|
const template = hbs`<UserMenu::MenuItem @item={{this.item}}/>`;
|
||||||
|
|
||||||
test("uses bookmarkable_url for the href", async function (assert) {
|
test("uses bookmarkable_url for the href", async function (assert) {
|
||||||
this.set("item", getBookmark());
|
this.set("item", getBookmark({}, this.siteSettings, this.site));
|
||||||
await render(template);
|
await render(template);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
query("li.bookmark a").href.endsWith("/t/this-bookmarkable-url/227/1")
|
query("li.bookmark a").href.endsWith("/t/this-bookmarkable-url/227/1")
|
||||||
|
@ -501,7 +505,11 @@ module(
|
||||||
test("item label is the bookmarked post author", async function (assert) {
|
test("item label is the bookmarked post author", async function (assert) {
|
||||||
this.set(
|
this.set(
|
||||||
"item",
|
"item",
|
||||||
getBookmark({ user: { username: "bookmarkPostAuthor" } })
|
getBookmark(
|
||||||
|
{ user: { username: "bookmarkPostAuthor" } },
|
||||||
|
this.siteSettings,
|
||||||
|
this.site
|
||||||
|
)
|
||||||
);
|
);
|
||||||
await render(template);
|
await render(template);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
|
@ -511,7 +519,14 @@ module(
|
||||||
});
|
});
|
||||||
|
|
||||||
test("item description is the bookmark title", async function (assert) {
|
test("item description is the bookmark title", async function (assert) {
|
||||||
this.set("item", getBookmark({ title: "Custom bookmark title" }));
|
this.set(
|
||||||
|
"item",
|
||||||
|
getBookmark(
|
||||||
|
{ title: "Custom bookmark title" },
|
||||||
|
this.siteSettings,
|
||||||
|
this.site
|
||||||
|
)
|
||||||
|
);
|
||||||
await render(template);
|
await render(template);
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
query("li.bookmark .item-description").textContent.trim(),
|
query("li.bookmark .item-description").textContent.trim(),
|
||||||
|
|
|
@ -514,6 +514,7 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.d-icon {
|
.d-icon {
|
||||||
padding-top: 0.2em;
|
padding-top: 0.2em;
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
@ -557,6 +558,42 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-menu.show-avatars {
|
||||||
|
li {
|
||||||
|
a {
|
||||||
|
.icon-avatar {
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
overflow: visible;
|
||||||
|
margin-right: 0.5em;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 2.25em;
|
||||||
|
height: 2.25em;
|
||||||
|
padding: 0.125em 0;
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.d-icon {
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: -0.675em;
|
||||||
|
bottom: -0.125em;
|
||||||
|
background: var(--secondary);
|
||||||
|
color: var(--primary-very-high);
|
||||||
|
padding: 0.25em;
|
||||||
|
border-radius: 100%;
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& + div {
|
||||||
|
padding: 0.25em 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.hamburger-panel .menu-panel.slide-in {
|
.hamburger-panel .menu-panel.slide-in {
|
||||||
left: 0;
|
left: 0;
|
||||||
|
|
||||||
|
|
|
@ -57,6 +57,8 @@ class NotificationsController < ApplicationController
|
||||||
|
|
||||||
notifications =
|
notifications =
|
||||||
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
||||||
|
notifications =
|
||||||
|
Notification.populate_acting_user(notifications) if SiteSetting.show_user_menu_avatars
|
||||||
|
|
||||||
json = {
|
json = {
|
||||||
notifications: serialize_data(notifications, NotificationSerializer),
|
notifications: serialize_data(notifications, NotificationSerializer),
|
||||||
|
@ -86,6 +88,8 @@ class NotificationsController < ApplicationController
|
||||||
notifications = notifications.offset(offset).limit(limit)
|
notifications = notifications.offset(offset).limit(limit)
|
||||||
notifications =
|
notifications =
|
||||||
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
Notification.filter_inaccessible_topic_notifications(current_user.guardian, notifications)
|
||||||
|
notifications =
|
||||||
|
Notification.populate_acting_user(notifications) if SiteSetting.show_user_menu_avatars
|
||||||
render_json_dump(
|
render_json_dump(
|
||||||
notifications: serialize_data(notifications, NotificationSerializer),
|
notifications: serialize_data(notifications, NotificationSerializer),
|
||||||
total_rows_notifications: total_rows,
|
total_rows_notifications: total_rows,
|
||||||
|
|
|
@ -1897,6 +1897,9 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
if reminder_notifications.present?
|
if reminder_notifications.present?
|
||||||
|
if SiteSetting.show_user_menu_avatars
|
||||||
|
Notification.populate_acting_user(reminder_notifications)
|
||||||
|
end
|
||||||
serialized_notifications =
|
serialized_notifications =
|
||||||
ActiveModel::ArraySerializer.new(
|
ActiveModel::ArraySerializer.new(
|
||||||
reminder_notifications,
|
reminder_notifications,
|
||||||
|
@ -1967,6 +1970,7 @@ class UsersController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
if unread_notifications.present?
|
if unread_notifications.present?
|
||||||
|
Notification.populate_acting_user(unread_notifications) if SiteSetting.show_user_menu_avatars
|
||||||
serialized_unread_notifications =
|
serialized_unread_notifications =
|
||||||
ActiveModel::ArraySerializer.new(
|
ActiveModel::ArraySerializer.new(
|
||||||
unread_notifications,
|
unread_notifications,
|
||||||
|
@ -1978,9 +1982,17 @@ class UsersController < ApplicationController
|
||||||
if messages_list
|
if messages_list
|
||||||
serialized_messages =
|
serialized_messages =
|
||||||
serialize_data(messages_list, TopicListSerializer, scope: guardian, root: false)[:topics]
|
serialize_data(messages_list, TopicListSerializer, scope: guardian, root: false)[:topics]
|
||||||
|
serialized_users =
|
||||||
|
if SiteSetting.show_user_menu_avatars
|
||||||
|
users = messages_list.topics.map { |t| t.posters.last.user }.flatten.compact.uniq(&:id)
|
||||||
|
serialize_data(users, BasicUserSerializer, scope: guardian, root: false)
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
if read_notifications.present?
|
if read_notifications.present?
|
||||||
|
Notification.populate_acting_user(read_notifications) if SiteSetting.show_user_menu_avatars
|
||||||
serialized_read_notifications =
|
serialized_read_notifications =
|
||||||
ActiveModel::ArraySerializer.new(
|
ActiveModel::ArraySerializer.new(
|
||||||
read_notifications,
|
read_notifications,
|
||||||
|
@ -1993,6 +2005,7 @@ class UsersController < ApplicationController
|
||||||
unread_notifications: serialized_unread_notifications || [],
|
unread_notifications: serialized_unread_notifications || [],
|
||||||
read_notifications: serialized_read_notifications || [],
|
read_notifications: serialized_read_notifications || [],
|
||||||
topics: serialized_messages || [],
|
topics: serialized_messages || [],
|
||||||
|
users: serialized_users || [],
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Notification < ActiveRecord::Base
|
class Notification < ActiveRecord::Base
|
||||||
|
attr_accessor :acting_user
|
||||||
|
attr_accessor :acting_username
|
||||||
|
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :topic
|
belongs_to :topic
|
||||||
|
|
||||||
|
@ -349,6 +352,25 @@ class Notification < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.populate_acting_user(notifications)
|
||||||
|
usernames =
|
||||||
|
notifications.map do |notification|
|
||||||
|
notification.acting_username =
|
||||||
|
(
|
||||||
|
notification.data_hash[:username] || notification.data_hash[:display_username] ||
|
||||||
|
notification.data_hash[:mentioned_by_username] ||
|
||||||
|
notification.data_hash[:invited_by_username]
|
||||||
|
)&.downcase
|
||||||
|
end
|
||||||
|
|
||||||
|
users = User.where(username_lower: usernames.uniq).index_by(&:username_lower)
|
||||||
|
notifications.each do |notification|
|
||||||
|
notification.acting_user = users[notification.acting_username]
|
||||||
|
end
|
||||||
|
|
||||||
|
notifications
|
||||||
|
end
|
||||||
|
|
||||||
def unread_high_priority?
|
def unread_high_priority?
|
||||||
self.high_priority? && !read
|
self.high_priority? && !read
|
||||||
end
|
end
|
||||||
|
|
|
@ -13,7 +13,8 @@ class NotificationSerializer < ApplicationSerializer
|
||||||
:fancy_title,
|
:fancy_title,
|
||||||
:slug,
|
:slug,
|
||||||
:data,
|
:data,
|
||||||
:is_warning
|
:is_warning,
|
||||||
|
:acting_user_avatar_template
|
||||||
|
|
||||||
def slug
|
def slug
|
||||||
Slug.for(object.topic.title) if object.topic.present?
|
Slug.for(object.topic.title) if object.topic.present?
|
||||||
|
@ -46,4 +47,12 @@ class NotificationSerializer < ApplicationSerializer
|
||||||
def include_external_id?
|
def include_external_id?
|
||||||
SiteSetting.enable_discourse_connect
|
SiteSetting.enable_discourse_connect
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def acting_user_avatar_template
|
||||||
|
object.acting_user.avatar_template_url
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_acting_user_avatar_template?
|
||||||
|
object.acting_user.present?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -46,6 +46,7 @@ class SiteSerializer < ApplicationSerializer
|
||||||
:denied_emojis,
|
:denied_emojis,
|
||||||
:tos_url,
|
:tos_url,
|
||||||
:privacy_policy_url,
|
:privacy_policy_url,
|
||||||
|
:system_user_avatar_template,
|
||||||
)
|
)
|
||||||
|
|
||||||
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||||
|
@ -332,6 +333,14 @@ class SiteSerializer < ApplicationSerializer
|
||||||
privacy_policy_url.present?
|
privacy_policy_url.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def system_user_avatar_template
|
||||||
|
Discourse.system_user.avatar_template
|
||||||
|
end
|
||||||
|
|
||||||
|
def include_system_user_avatar_template?
|
||||||
|
SiteSetting.show_user_menu_avatars
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def ordered_flags(flags)
|
def ordered_flags(flags)
|
||||||
|
|
|
@ -2484,6 +2484,7 @@ en:
|
||||||
experimental_form_templates: "EXPERIMENTAL: Enable the form templates feature. <b>After enabled,</b> manage the templates at <a href='%{base_path}/admin/customize/form-templates'>Customize / Templates</a>."
|
experimental_form_templates: "EXPERIMENTAL: Enable the form templates feature. <b>After enabled,</b> manage the templates at <a href='%{base_path}/admin/customize/form-templates'>Customize / Templates</a>."
|
||||||
|
|
||||||
page_loading_indicator: "Configure the loading indicator which appears during page navigations within Discourse. 'Spinner' is a full page indicator. 'Slider' shows a narrow bar at the top of the screen."
|
page_loading_indicator: "Configure the loading indicator which appears during page navigations within Discourse. 'Spinner' is a full page indicator. 'Slider' shows a narrow bar at the top of the screen."
|
||||||
|
show_user_menu_avatars: "Show user avatars in the user menu"
|
||||||
|
|
||||||
errors:
|
errors:
|
||||||
invalid_css_color: "Invalid color. Enter a color name or hex value."
|
invalid_css_color: "Invalid color. Enter a color name or hex value."
|
||||||
|
|
|
@ -395,7 +395,9 @@ basic:
|
||||||
choices:
|
choices:
|
||||||
- spinner
|
- spinner
|
||||||
- slider
|
- slider
|
||||||
|
show_user_menu_avatars:
|
||||||
|
client: true
|
||||||
|
default: false
|
||||||
login:
|
login:
|
||||||
invite_only:
|
invite_only:
|
||||||
refresh: true
|
refresh: true
|
||||||
|
|
|
@ -806,4 +806,34 @@ RSpec.describe Notification do
|
||||||
expect(notification.shelved_notification).to be_nil
|
expect(notification.shelved_notification).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe ".populate_acting_user" do
|
||||||
|
fab!(:user1) { Fabricate(:user) }
|
||||||
|
fab!(:user2) { Fabricate(:user) }
|
||||||
|
fab!(:user3) { Fabricate(:user) }
|
||||||
|
fab!(:user4) { Fabricate(:user) }
|
||||||
|
fab!(:notification1) do
|
||||||
|
Fabricate(:notification, user: user, data: { username: user1.username }.to_json)
|
||||||
|
end
|
||||||
|
fab!(:notification2) do
|
||||||
|
Fabricate(:notification, user: user, data: { display_username: user2.username }.to_json)
|
||||||
|
end
|
||||||
|
fab!(:notification3) do
|
||||||
|
Fabricate(:notification, user: user, data: { mentioned_by_username: user3.username }.to_json)
|
||||||
|
end
|
||||||
|
fab!(:notification4) do
|
||||||
|
Fabricate(:notification, user: user, data: { invited_by_username: user4.username }.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "Sets the acting_user correctly for each notification" do
|
||||||
|
Notification.populate_acting_user(
|
||||||
|
[notification1, notification2, notification3, notification4],
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(notification1.acting_user).to eq(user1)
|
||||||
|
expect(notification2.acting_user).to eq(user2)
|
||||||
|
expect(notification3.acting_user).to eq(user3)
|
||||||
|
expect(notification4.acting_user).to eq(user4)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -32,7 +32,10 @@ RSpec.describe NotificationsController do
|
||||||
context "when logged in" do
|
context "when logged in" do
|
||||||
context "as normal user" do
|
context "as normal user" do
|
||||||
fab!(:user) { sign_in(Fabricate(:user)) }
|
fab!(:user) { sign_in(Fabricate(:user)) }
|
||||||
fab!(:notification) { Fabricate(:notification, user: user) }
|
fab!(:acting_user) { Fabricate(:user) }
|
||||||
|
fab!(:notification) do
|
||||||
|
Fabricate(:notification, user: user, data: { username: acting_user.username }.to_json)
|
||||||
|
end
|
||||||
|
|
||||||
describe "#index" do
|
describe "#index" do
|
||||||
it "should succeed for recent" do
|
it "should succeed for recent" do
|
||||||
|
@ -424,6 +427,20 @@ RSpec.describe NotificationsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with `show_user_menu_avatars` setting enabled" do
|
||||||
|
before { SiteSetting.show_user_menu_avatars = true }
|
||||||
|
|
||||||
|
it "serializes acting_user_avatar_template into notifications" do
|
||||||
|
get "/notifications.json"
|
||||||
|
|
||||||
|
notifications = response.parsed_body["notifications"]
|
||||||
|
expect(notifications).not_to be_empty
|
||||||
|
notifications.each do |notification|
|
||||||
|
expect(notification["acting_user_avatar_template"]).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should succeed" do
|
it "should succeed" do
|
||||||
|
|
|
@ -7051,6 +7051,18 @@ RSpec.describe UsersController do
|
||||||
expect(notifications.size).to eq(1)
|
expect(notifications.size).to eq(1)
|
||||||
expect(notifications.first["data"]["bookmark_id"]).to eq(bookmark_with_reminder.id)
|
expect(notifications.first["data"]["bookmark_id"]).to eq(bookmark_with_reminder.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "with `show_user_menu_avatars` setting enabled" do
|
||||||
|
before { SiteSetting.show_user_menu_avatars = true }
|
||||||
|
|
||||||
|
it "serializes acting_user_avatar into notifications" do
|
||||||
|
get "/u/#{user.username}/user-menu-bookmarks"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
first_notification = response.parsed_body["notifications"].first
|
||||||
|
expect(first_notification["acting_user_avatar_template"]).to be_present
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue