This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/widgets/user-menu.js.es6
Kyle Zhao 9b10a78d82 FEATURE: Quick access panels in user menu (#8073)
* Extract QuickAccessPanel from UserNotifications.

* FEATURE: Quick access panels in user menu.

This feature adds quick access panels for bookmarks and personal
messages. It allows uses to browse recent items directly in the user
menu, without being redirected to the full pages.

* REFACTOR: Use QuickAccessItem for messages.

Reusing `DefaultNotificationItem` feels nice but it actually requires a
lot of extra work that is not needed for a quick access item.

Also, `DefaultNotificationItem` shows an incorrect tooptip ("unread
private message"), and it is not trivial to remove / override that.

* Use a plain JS object instead.

An Ember object was required when `DefaultNotificationItem` was used.

* Prefix instead suffix `_` for private helpers.

* Set to null instead of deleting object keys.

JavaScript engines can optimize object property access based on the
object’s shape. https://mathiasbynens.be/notes/shapes-ics

* Change trivial try/catch to one-liners.

* Return the promise in case needs to be waited on.

* Refactor showAll to a link with href

* Store `emptyStatePlaceholderItemText` in state.

* Store items in Session singleton instead.

We can drop `staleItems` (and `findStaleItems`) altogether. Because
`(old) items === staleItems` when switching back to a quick access
panel.

* Add `limit` parameter to the `user_actions` API.

* Explicitly import Session instead.
2019-09-09 11:03:57 -04:00

257 lines
5.9 KiB
JavaScript

import { createWidget } from "discourse/widgets/widget";
import { h } from "virtual-dom";
import { formatUsername } from "discourse/lib/utilities";
import hbs from "discourse/widgets/hbs-compiler";
const UserMenuAction = {
QUICK_ACCESS: "quickAccess"
};
const QuickAccess = {
BOOKMARKS: "bookmarks",
MESSAGES: "messages",
NOTIFICATIONS: "notifications",
PROFILE: "profile"
};
let extraGlyphs;
export function addUserMenuGlyph(glyph) {
extraGlyphs = extraGlyphs || [];
extraGlyphs.push(glyph);
}
createWidget("user-menu-links", {
tagName: "div.menu-links-header",
profileLink() {
const link = {
action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.PROFILE,
route: "user",
model: this.currentUser,
className: "user-activity-link",
icon: "user",
rawLabel: formatUsername(this.currentUser.username)
};
if (this.currentUser.is_anonymous) {
link.label = "user.profile";
link.rawLabel = null;
}
return link;
},
notificationsGlyph() {
return {
label: "user.notifications",
className: "user-notifications-link",
icon: "bell",
href: `${this.attrs.path}/notifications`,
action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.NOTIFICATIONS
};
},
bookmarksGlyph() {
return {
action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.BOOKMARKS,
label: "user.bookmarks",
className: "user-bookmarks-link",
icon: "bookmark",
href: `${this.attrs.path}/activity/bookmarks`
};
},
messagesGlyph() {
return {
action: UserMenuAction.QUICK_ACCESS,
actionParam: QuickAccess.MESSAGES,
label: "user.private_messages",
className: "user-pms-link",
icon: "envelope",
href: `${this.attrs.path}/messages`
};
},
linkHtml(link) {
if (this.isActive(link)) {
link = this.markAsActive(link);
}
return this.attach("link", link);
},
glyphHtml(glyph) {
if (this.isActive(glyph)) {
glyph = this.markAsActive(glyph);
}
return this.attach("link", $.extend(glyph, { hideLabel: true }));
},
html() {
const links = [this.profileLink()];
const glyphs = [];
if (extraGlyphs) {
extraGlyphs.forEach(g => {
if (typeof g === "function") {
g = g(this);
}
if (g) {
glyphs.push(g);
}
});
}
glyphs.push(this.notificationsGlyph());
glyphs.push(this.bookmarksGlyph());
if (this.siteSettings.enable_personal_messages) {
glyphs.push(this.messagesGlyph());
}
return h("ul.menu-links-row", [
links.map(l => h("li.user", this.linkHtml(l))),
h("li.glyphs", glyphs.map(l => this.glyphHtml(l)))
]);
},
markAsActive(definition) {
// Clicking on an active quick access tab icon should redirect the user to
// the full page.
definition.action = null;
definition.actionParam = null;
if (definition.className) {
definition.className += " active";
} else {
definition.className = "active";
}
return definition;
},
isActive({ action, actionParam }) {
return (
action === UserMenuAction.QUICK_ACCESS &&
actionParam === this.attrs.currentQuickAccess
);
}
});
createWidget("user-menu-dismiss-link", {
tagName: "div.dismiss-link",
template: hbs`
<ul class='menu-links'>
<li>
{{link action="dismissNotifications"
className="dismiss"
tabindex="0"
icon="check"
label="user.dismiss"
title="user.dismiss_notifications_tooltip"}}
</li>
</ul>
`
});
export default createWidget("user-menu", {
tagName: "div.user-menu",
buildKey: () => "user-menu",
settings: {
maxWidth: 320,
showLogoutButton: true
},
defaultState() {
return {
currentQuickAccess: QuickAccess.NOTIFICATIONS,
hasUnread: false,
markUnread: null
};
},
panelContents() {
const path = this.currentUser.get("path");
const { currentQuickAccess } = this.state;
const result = [
this.attach("user-menu-links", {
path,
currentQuickAccess
}),
this.quickAccessPanel(path)
];
if (this.state.hasUnread) {
result.push(h("hr.bottom-area"));
result.push(this.attach("user-menu-dismiss-link"));
}
return result;
},
dismissNotifications() {
return this.state.markRead();
},
itemsLoaded({ hasUnread, markRead }) {
this.state.hasUnread = hasUnread;
this.state.markRead = markRead;
},
html() {
return this.attach("menu-panel", {
maxWidth: this.settings.maxWidth,
contents: () => this.panelContents()
});
},
clickOutsideMobile(e) {
const $centeredElement = $(document.elementFromPoint(e.clientX, e.clientY));
if (
$centeredElement.parents(".panel").length &&
!$centeredElement.hasClass("header-cloak")
) {
this.sendWidgetAction("toggleUserMenu");
} else {
const $window = $(window);
const windowWidth = parseInt($window.width(), 10);
const $panel = $(".menu-panel");
$panel.addClass("animate");
$panel.css("right", -windowWidth);
const $headerCloak = $(".header-cloak");
$headerCloak.addClass("animate");
$headerCloak.css("opacity", 0);
Ember.run.later(() => this.sendWidgetAction("toggleUserMenu"), 200);
}
},
clickOutside(e) {
if (this.site.mobileView) {
this.clickOutsideMobile(e);
} else {
this.sendWidgetAction("toggleUserMenu");
}
},
quickAccess(type) {
if (this.state.currentQuickAccess !== type) {
this.state.currentQuickAccess = type;
}
},
quickAccessPanel(path) {
const { showLogoutButton } = this.settings;
// This deliberately does NOT fallback to a default quick access panel.
return this.attach(`quick-access-${this.state.currentQuickAccess}`, {
path,
showLogoutButton
});
}
});