mirror of
https://github.com/discourse/discourse.git
synced 2025-06-04 20:04:42 +08:00
DEV: Render glimmer notification items for user notification list (#24802)
This removes the widget notifications list and renders the glimmer user menu notification items instead.
This commit is contained in:

committed by
GitHub

parent
4904c2f11b
commit
223e413a6c
@ -0,0 +1,32 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { longDate, relativeAge } from "discourse/lib/formatter";
|
||||
|
||||
export default class RelativeDate extends Component {
|
||||
get datetime() {
|
||||
if (this.memoizedDatetime) {
|
||||
return this.memoizedDatetime;
|
||||
}
|
||||
|
||||
this.memoizedDatetime = new Date(this.args.date);
|
||||
return this.memoizedDatetime;
|
||||
}
|
||||
|
||||
get title() {
|
||||
return longDate(this.datetime);
|
||||
}
|
||||
|
||||
get time() {
|
||||
return this.datetime.getTime();
|
||||
}
|
||||
|
||||
<template>
|
||||
<span
|
||||
class="relative-date"
|
||||
title={{this.title}}
|
||||
data-time={{this.time}}
|
||||
data-format="tiny"
|
||||
>
|
||||
{{relativeAge this.datetime}}
|
||||
</span>
|
||||
</template>
|
||||
}
|
@ -24,5 +24,11 @@
|
||||
</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<PluginOutlet @name="menu-item-end" @outletArgs={{hash item=this}}>
|
||||
{{#if this.endComponent}}
|
||||
<this.endComponent />
|
||||
{{/if}}
|
||||
</PluginOutlet>
|
||||
</a>
|
||||
</li>
|
@ -57,6 +57,10 @@ export default class UserMenuItem extends Component {
|
||||
return this.#item.iconComponentArgs;
|
||||
}
|
||||
|
||||
get endComponent() {
|
||||
return this.#item.endComponent;
|
||||
}
|
||||
|
||||
get #item() {
|
||||
return this.args.item;
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
import MountWidget from "discourse/components/mount-widget";
|
||||
import { observes } from "discourse-common/utils/decorators";
|
||||
|
||||
export default MountWidget.extend({
|
||||
widget: "user-notifications-large",
|
||||
notifications: null,
|
||||
args: null,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
|
||||
this.args = { notifications: this.notifications };
|
||||
},
|
||||
|
||||
@observes("notifications.length", "notifications.@each.read")
|
||||
_triggerRefresh() {
|
||||
this.set("args", {
|
||||
notifications: this.notifications,
|
||||
});
|
||||
|
||||
this.queueRerender();
|
||||
},
|
||||
});
|
@ -0,0 +1,107 @@
|
||||
import Controller from "@ember/controller";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DismissNotificationConfirmationModal from "discourse/components/modal/dismiss-notification-confirmation";
|
||||
import RelativeDate from "discourse/components/relative-date";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import UserMenuNotificationItem from "discourse/lib/user-menu/notification-item";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default class UserNotificationsController extends Controller {
|
||||
@service modal;
|
||||
@service appEvents;
|
||||
@service currentUser;
|
||||
@service site;
|
||||
@service siteSettings;
|
||||
|
||||
queryParams = ["filter"];
|
||||
filter = "all";
|
||||
|
||||
get listContainerClassNames() {
|
||||
return `user-notifications-list ${
|
||||
this.siteSettings.show_user_menu_avatars ? "show-avatars" : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
@discourseComputed("filter")
|
||||
isFiltered() {
|
||||
return this.filter && this.filter !== "all";
|
||||
}
|
||||
|
||||
@discourseComputed("model.content.@each")
|
||||
items() {
|
||||
return this.model.map((notification) => {
|
||||
const props = {
|
||||
appEvents: this.appEvents,
|
||||
currentUser: this.currentUser,
|
||||
siteSettings: this.siteSettings,
|
||||
site: this.site,
|
||||
notification,
|
||||
endComponent: <template>
|
||||
<RelativeDate @date={{notification.created_at}} />
|
||||
</template>,
|
||||
};
|
||||
return new UserMenuNotificationItem(props);
|
||||
});
|
||||
}
|
||||
|
||||
@discourseComputed("model.content.@each.read")
|
||||
allNotificationsRead() {
|
||||
return !this.get("model.content").some(
|
||||
(notification) => !notification.get("read")
|
||||
);
|
||||
}
|
||||
|
||||
@discourseComputed("isFiltered", "model.content.length")
|
||||
doesNotHaveNotifications(isFiltered, contentLength) {
|
||||
return !isFiltered && contentLength === 0;
|
||||
}
|
||||
|
||||
@discourseComputed("isFiltered", "model.content.length")
|
||||
nothingFound(isFiltered, contentLength) {
|
||||
return isFiltered && contentLength === 0;
|
||||
}
|
||||
|
||||
@discourseComputed()
|
||||
emptyStateBody() {
|
||||
return htmlSafe(
|
||||
I18n.t("user.no_notifications_page_body", {
|
||||
preferencesUrl: getURL("/my/preferences/notifications"),
|
||||
icon: iconHTML("bell"),
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async markRead() {
|
||||
await ajax("/notifications/mark-read", { type: "PUT" });
|
||||
this.model.forEach((notification) => notification.set("read", true));
|
||||
}
|
||||
|
||||
@action
|
||||
async resetNew() {
|
||||
if (this.currentUser.unread_high_priority_notifications > 0) {
|
||||
this.modal.show(DismissNotificationConfirmationModal, {
|
||||
model: {
|
||||
confirmationMessage: I18n.t(
|
||||
"notifications.dismiss_confirmation.body.default",
|
||||
{
|
||||
count: this.currentUser.unread_high_priority_notifications,
|
||||
}
|
||||
),
|
||||
dismissNotifications: () => this.markRead(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.markRead();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
loadMore() {
|
||||
this.model.loadMore();
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
import Controller from "@ember/controller";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import DismissNotificationConfirmationModal from "discourse/components/modal/dismiss-notification-confirmation";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { iconHTML } from "discourse-common/lib/icon-library";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
export default Controller.extend({
|
||||
modal: service(),
|
||||
queryParams: ["filter"],
|
||||
filter: "all",
|
||||
|
||||
@discourseComputed("filter")
|
||||
isFiltered() {
|
||||
return this.filter && this.filter !== "all";
|
||||
},
|
||||
|
||||
@discourseComputed("model.content.@each.read")
|
||||
allNotificationsRead() {
|
||||
return !this.get("model.content").some(
|
||||
(notification) => !notification.get("read")
|
||||
);
|
||||
},
|
||||
|
||||
@discourseComputed("isFiltered", "model.content.length")
|
||||
doesNotHaveNotifications(isFiltered, contentLength) {
|
||||
return !isFiltered && contentLength === 0;
|
||||
},
|
||||
|
||||
@discourseComputed("isFiltered", "model.content.length")
|
||||
nothingFound(isFiltered, contentLength) {
|
||||
return isFiltered && contentLength === 0;
|
||||
},
|
||||
|
||||
@discourseComputed()
|
||||
emptyStateBody() {
|
||||
return htmlSafe(
|
||||
I18n.t("user.no_notifications_page_body", {
|
||||
preferencesUrl: getURL("/my/preferences/notifications"),
|
||||
icon: iconHTML("bell"),
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
async markRead() {
|
||||
await ajax("/notifications/mark-read", { type: "PUT" });
|
||||
this.model.forEach((n) => n.set("read", true));
|
||||
},
|
||||
|
||||
actions: {
|
||||
async resetNew() {
|
||||
if (this.currentUser.unread_high_priority_notifications > 0) {
|
||||
this.modal.show(DismissNotificationConfirmationModal, {
|
||||
model: {
|
||||
confirmationMessage: I18n.t(
|
||||
"notifications.dismiss_confirmation.body.default",
|
||||
{
|
||||
count: this.currentUser.unread_high_priority_notifications,
|
||||
}
|
||||
),
|
||||
dismissNotifications: () => this.markRead(),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.markRead();
|
||||
}
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
this.model.loadMore();
|
||||
},
|
||||
},
|
||||
});
|
@ -5,11 +5,19 @@ import UserMenuBaseItem from "discourse/lib/user-menu/base-item";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
|
||||
export default class UserMenuNotificationItem extends UserMenuBaseItem {
|
||||
constructor({ notification, appEvents, currentUser, siteSettings, site }) {
|
||||
constructor({
|
||||
notification,
|
||||
endComponent,
|
||||
appEvents,
|
||||
currentUser,
|
||||
siteSettings,
|
||||
site,
|
||||
}) {
|
||||
super(...arguments);
|
||||
this.appEvents = appEvents;
|
||||
this.notification = notification;
|
||||
this.currentUser = currentUser;
|
||||
this.endComponent = endComponent;
|
||||
this.notification = notification;
|
||||
this.siteSettings = siteSettings;
|
||||
this.site = site;
|
||||
|
||||
|
@ -22,7 +22,11 @@
|
||||
{{#if this.nothingFound}}
|
||||
<div class="alert alert-info">{{i18n "notifications.empty"}}</div>
|
||||
{{else}}
|
||||
<UserNotificationsLarge @notifications={{this.model}} />
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
<div class={{this.listContainerClassNames}}>
|
||||
{{#each this.items as |item|}}
|
||||
<UserMenu::MenuItem @item={{item}} />
|
||||
{{/each}}
|
||||
<ConditionalLoadingSpinner @condition={{this.loading}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
@ -1,19 +0,0 @@
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { iconNode } from "discourse-common/lib/icon-library";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(DefaultNotificationItem, "admin-problems-notification-item", {
|
||||
text() {
|
||||
return I18n.t("notifications.admin_problems");
|
||||
},
|
||||
|
||||
url() {
|
||||
return getURL("/admin");
|
||||
},
|
||||
|
||||
icon() {
|
||||
return iconNode("gift");
|
||||
},
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"bookmark-reminder-notification-item",
|
||||
{
|
||||
text(notificationName, data) {
|
||||
const username = formatUsername(data.display_username);
|
||||
const description = this.description(data);
|
||||
|
||||
return I18n.t("notifications.bookmark_reminder", {
|
||||
description,
|
||||
username,
|
||||
});
|
||||
},
|
||||
|
||||
notificationTitle(notificationName, data) {
|
||||
if (notificationName) {
|
||||
if (data.bookmark_name) {
|
||||
return I18n.t(`notifications.titles.${notificationName}_with_name`, {
|
||||
name: data.bookmark_name,
|
||||
});
|
||||
} else {
|
||||
return I18n.t(`notifications.titles.${notificationName}`);
|
||||
}
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
@ -1,22 +0,0 @@
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import { iconNode } from "discourse-common/lib/icon-library";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(DefaultNotificationItem, "custom-notification-item", {
|
||||
notificationTitle(notificationName, data) {
|
||||
return data.title ? I18n.t(data.title) : "";
|
||||
},
|
||||
|
||||
text(notificationName, data) {
|
||||
const username = formatUsername(data.display_username);
|
||||
const description = this.description(data);
|
||||
|
||||
return I18n.t(data.message, { description, username });
|
||||
},
|
||||
|
||||
icon(notificationName, data) {
|
||||
return iconNode(`notification.${data.message}`);
|
||||
},
|
||||
});
|
@ -1,19 +0,0 @@
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"group-message-summary-notification-item",
|
||||
{
|
||||
text(notificationName, data) {
|
||||
const count = data.inbox_count;
|
||||
const group_name = data.group_name;
|
||||
|
||||
return I18n.t("notifications.group_message_summary", {
|
||||
count,
|
||||
group_name,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
@ -1,13 +0,0 @@
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"invitee-accepted-notification-item",
|
||||
{
|
||||
url(data) {
|
||||
return userPath(data.display_username);
|
||||
},
|
||||
}
|
||||
);
|
@ -1,31 +0,0 @@
|
||||
import { isEmpty } from "@ember/utils";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { escapeExpression } from "discourse/lib/utilities";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"liked-consolidated-notification-item",
|
||||
{
|
||||
url(data) {
|
||||
return userPath(
|
||||
`${
|
||||
this.attrs.username || this.currentUser.username
|
||||
}/notifications/likes-received?acting_username=${data.display_username}`
|
||||
);
|
||||
},
|
||||
|
||||
description(data) {
|
||||
const description = I18n.t(
|
||||
"notifications.liked_consolidated_description",
|
||||
{
|
||||
count: parseInt(data.count, 10),
|
||||
}
|
||||
);
|
||||
|
||||
return isEmpty(description) ? "" : escapeExpression(description);
|
||||
},
|
||||
}
|
||||
);
|
@ -1,32 +0,0 @@
|
||||
import { formatUsername } from "discourse/lib/utilities";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(DefaultNotificationItem, "liked-notification-item", {
|
||||
text(notificationName, data) {
|
||||
const username = formatUsername(data.display_username);
|
||||
const description = this.description(data);
|
||||
|
||||
if (data.count > 1) {
|
||||
const count = data.count - 1;
|
||||
const username2 = formatUsername(data.username2);
|
||||
|
||||
if (count === 0) {
|
||||
return I18n.t("notifications.liked_2", {
|
||||
description,
|
||||
username: `<span class="multi-username">${username}</span>`,
|
||||
username2: `<span class="multi-username">${username2}</span>`,
|
||||
});
|
||||
} else {
|
||||
return I18n.t("notifications.liked_many", {
|
||||
description,
|
||||
username: `<span class="multi-username">${username}</span>`,
|
||||
count,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return I18n.t("notifications.liked", { description, username });
|
||||
},
|
||||
});
|
@ -1,20 +0,0 @@
|
||||
import { groupPath } from "discourse/lib/url";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"membership-request-accepted-notification-item",
|
||||
{
|
||||
url(data) {
|
||||
return groupPath(data.group_name);
|
||||
},
|
||||
|
||||
text(notificationName, data) {
|
||||
return I18n.t(`notifications.${notificationName}`, {
|
||||
group_name: data.group_name,
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
@ -1,23 +0,0 @@
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(
|
||||
DefaultNotificationItem,
|
||||
"membership-request-consolidated-notification-item",
|
||||
{
|
||||
url() {
|
||||
return userPath(
|
||||
`${this.attrs.username || this.currentUser.username}/messages`
|
||||
);
|
||||
},
|
||||
|
||||
text(notificationName, data) {
|
||||
return I18n.t("notifications.membership_request_consolidated", {
|
||||
group_name: data.group_name,
|
||||
count: parseInt(data.count, 10),
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
@ -1,19 +0,0 @@
|
||||
import { DefaultNotificationItem } from "discourse/widgets/default-notification-item";
|
||||
import { createWidgetFrom } from "discourse/widgets/widget";
|
||||
import getURL from "discourse-common/lib/get-url";
|
||||
import { iconNode } from "discourse-common/lib/icon-library";
|
||||
import I18n from "discourse-i18n";
|
||||
|
||||
createWidgetFrom(DefaultNotificationItem, "new-features-notification-item", {
|
||||
text() {
|
||||
return I18n.t("notifications.new_features");
|
||||
},
|
||||
|
||||
url() {
|
||||
return getURL("/admin");
|
||||
},
|
||||
|
||||
icon() {
|
||||
return iconNode("gift");
|
||||
},
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
import { dasherize } from "@ember/string";
|
||||
import { h } from "virtual-dom";
|
||||
import { dateNode } from "discourse/helpers/node";
|
||||
import { createWidget } from "discourse/widgets/widget";
|
||||
|
||||
createWidget("large-notification-item", {
|
||||
tagName: "li",
|
||||
|
||||
buildClasses(attrs) {
|
||||
const result = ["item", "notification", "large-notification"];
|
||||
if (!attrs.get("read")) {
|
||||
result.push("unread");
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
html(attrs) {
|
||||
const notificationName =
|
||||
this.site.notificationLookup[attrs.notification_type];
|
||||
|
||||
return [
|
||||
this.attach(
|
||||
`${dasherize(notificationName)}-notification-item`,
|
||||
attrs,
|
||||
{},
|
||||
{
|
||||
fallbackWidgetName: "default-notification-item",
|
||||
tagName: "div",
|
||||
}
|
||||
),
|
||||
h("span.time", dateNode(attrs.created_at)),
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
export default createWidget("user-notifications-large", {
|
||||
tagName: "ul.notifications.large-notifications",
|
||||
|
||||
html(attrs) {
|
||||
const notifications = attrs.notifications;
|
||||
const username = notifications.findArgs.username;
|
||||
|
||||
return notifications.map((n) => {
|
||||
n.username = username;
|
||||
return this.attach("large-notification-item", n);
|
||||
});
|
||||
},
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
import { visit } from "@ember/test-helpers";
|
||||
import { test } from "qunit";
|
||||
import { acceptance, exists } from "discourse/tests/helpers/qunit-helpers";
|
||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||
|
||||
acceptance("Notifications filter", function (needs) {
|
||||
needs.user();
|
||||
|
||||
test("Notifications filter true", async function (assert) {
|
||||
await visit("/u/eviltrout/notifications");
|
||||
|
||||
assert.ok(exists(".large-notification"));
|
||||
});
|
||||
|
||||
test("Notifications filter read", async function (assert) {
|
||||
await visit("/u/eviltrout/notifications");
|
||||
|
||||
const dropdown = selectKit(".notifications-filter");
|
||||
await dropdown.expand();
|
||||
await dropdown.selectRowByValue("read");
|
||||
|
||||
assert.ok(exists(".large-notification"));
|
||||
});
|
||||
|
||||
test("Notifications filter unread", async function (assert) {
|
||||
await visit("/u/eviltrout/notifications");
|
||||
|
||||
const dropdown = selectKit(".notifications-filter");
|
||||
await dropdown.expand();
|
||||
await dropdown.selectRowByValue("unread");
|
||||
|
||||
assert.ok(exists(".large-notification"));
|
||||
});
|
||||
});
|
@ -78,12 +78,10 @@ acceptance("User Routes", function (needs) {
|
||||
"has the body class"
|
||||
);
|
||||
|
||||
const $links = queryAll(".item.notification a");
|
||||
const $links = queryAll(".notification a");
|
||||
|
||||
assert.ok(
|
||||
$links[2].href.includes(
|
||||
"/u/eviltrout/notifications/likes-received?acting_username=aquaman"
|
||||
)
|
||||
$links[2].href.includes("/u/eviltrout/notifications/likes-received")
|
||||
);
|
||||
|
||||
updateCurrentUser({ moderator: true, admin: false });
|
||||
|
@ -163,40 +163,6 @@
|
||||
justify-content: space-between;
|
||||
box-sizing: border-box;
|
||||
min-width: 0; // makes sure menu tabs don't go off screen
|
||||
|
||||
.double-user,
|
||||
.multi-user {
|
||||
white-space: unset;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
li {
|
||||
background-color: var(--secondary);
|
||||
|
||||
&.unread,
|
||||
&.pending {
|
||||
background-color: var(--tertiary-low);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--d-hover);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background: var(--d-hover);
|
||||
a {
|
||||
// we don't need the link focus because we're styling the parent
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#quick-access-profile {
|
||||
@ -380,185 +346,218 @@
|
||||
}
|
||||
}
|
||||
|
||||
.user-menu {
|
||||
.quick-access-panel {
|
||||
width: 100%;
|
||||
// Panel / user-notification-list styles. **not** menu panel sizing styles
|
||||
.user-menu .quick-access-panel,
|
||||
.user-notifications-list {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
border-top: 1px solid var(--primary-low);
|
||||
padding-top: 0.75em;
|
||||
margin-top: -1px;
|
||||
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.double-user,
|
||||
.multi-user {
|
||||
white-space: unset;
|
||||
}
|
||||
|
||||
.item-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
h3 {
|
||||
padding: 0 0.4em;
|
||||
font-weight: bold;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.d-icon,
|
||||
&:hover .d-icon {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
.icon {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 0;
|
||||
flex-flow: column wrap;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
border-top: 1px solid var(--primary-low);
|
||||
padding-top: 0.75em;
|
||||
margin-top: -1px;
|
||||
&:focus {
|
||||
}
|
||||
|
||||
li {
|
||||
background-color: var(--secondary);
|
||||
box-sizing: border-box;
|
||||
list-style-type: none;
|
||||
|
||||
&.unread,
|
||||
&.pending {
|
||||
background-color: var(--tertiary-low);
|
||||
}
|
||||
&:hover {
|
||||
background-color: var(--d-hover);
|
||||
outline: none;
|
||||
}
|
||||
h3 {
|
||||
padding: 0 0.4em;
|
||||
font-weight: bold;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.d-icon,
|
||||
&:hover .d-icon {
|
||||
color: var(--primary-medium);
|
||||
}
|
||||
.icon {
|
||||
color: var(--primary-high);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
.d-icon {
|
||||
color: var(--secondary);
|
||||
&:focus-within {
|
||||
background: var(--d-hover);
|
||||
a {
|
||||
// we don't need the link focus because we're styling the parent
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
// This is until other languages remove the HTML from within
|
||||
// notifications. It can then be removed
|
||||
div .fa {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.double-user,
|
||||
// e.g., "username, username2"
|
||||
span.multi-user
|
||||
// e.g., "username and n others"
|
||||
{
|
||||
display: inline;
|
||||
max-width: 100%;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span.multi-user
|
||||
// e.g., "username, username2, and n others"
|
||||
{
|
||||
span.multi-username:nth-of-type(2) {
|
||||
// margin between username2 and "and n others"
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
// truncate when usernames are very long
|
||||
span.multi-username {
|
||||
@include ellipsis;
|
||||
flex: 0 1 auto;
|
||||
min-width: 1.2em;
|
||||
max-width: 10em;
|
||||
&:nth-of-type(2) {
|
||||
// margin for comma between username and username2
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--d-hover);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background: var(--d-hover);
|
||||
a {
|
||||
// we don't need the link focus because we're styling the parent
|
||||
outline: 0;
|
||||
}
|
||||
.btn-flat:focus {
|
||||
// undo default btn-flat style
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
.profile-tab-btn {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
margin: 0.25em;
|
||||
padding: 0em 0.25em;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
> div {
|
||||
overflow: hidden; // clears the text from wrapping below icons
|
||||
overflow-wrap: anywhere;
|
||||
@supports not (overflow-wrap: anywhere) {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// Truncate items with more than 2 lines.
|
||||
@include line-clamp(2);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
li {
|
||||
background-color: var(--d-selected);
|
||||
box-sizing: border-box;
|
||||
list-style-type: none;
|
||||
|
||||
// This is until other languages remove the HTML from within
|
||||
// notifications. It can then be removed
|
||||
div .fa {
|
||||
display: none;
|
||||
}
|
||||
|
||||
span.double-user,
|
||||
// e.g., "username, username2"
|
||||
span.multi-user
|
||||
// e.g., "username and n others"
|
||||
{
|
||||
display: inline;
|
||||
max-width: 100%;
|
||||
align-items: baseline;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
span.multi-user
|
||||
// e.g., "username, username2, and n others"
|
||||
{
|
||||
span.multi-username:nth-of-type(2) {
|
||||
// margin between username2 and "and n others"
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
// truncate when usernames are very long
|
||||
span.multi-username {
|
||||
@include ellipsis;
|
||||
flex: 0 1 auto;
|
||||
min-width: 1.2em;
|
||||
max-width: 10em;
|
||||
&:nth-of-type(2) {
|
||||
// margin for comma between username and username2
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--d-hover);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background: var(--d-hover);
|
||||
a {
|
||||
// we don't need the link focus because we're styling the parent
|
||||
outline: 0;
|
||||
}
|
||||
.btn-flat:focus {
|
||||
// undo default btn-flat style
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
a,
|
||||
.profile-tab-btn {
|
||||
display: flex;
|
||||
margin: 0.25em;
|
||||
padding: 0em 0.25em;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
a,
|
||||
button {
|
||||
> div {
|
||||
overflow: hidden; // clears the text from wrapping below icons
|
||||
overflow-wrap: anywhere;
|
||||
@supports not (overflow-wrap: anywhere) {
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
// Truncate items with more than 2 lines.
|
||||
@include line-clamp(2);
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
li:not(.show-all) {
|
||||
padding: 0;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
|
||||
.d-icon {
|
||||
padding-top: 0.2em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
.is-warning {
|
||||
.d-icon-envelope {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
.read {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
.none {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.spinner-container {
|
||||
min-height: 2em;
|
||||
}
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-width: 2px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.show-all a {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
color: var(--primary-med-or-secondary-high);
|
||||
background: var(--blend-primary-secondary-5);
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
background: var(--primary-low);
|
||||
}
|
||||
}
|
||||
/* as a big ol' click target, don't let text inside be selected */
|
||||
@include unselectable;
|
||||
}
|
||||
li:not(.show-all) {
|
||||
padding: 0;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
|
||||
.d-icon {
|
||||
padding-top: 0.2em;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
}
|
||||
.is-warning {
|
||||
.d-icon-envelope {
|
||||
color: var(--danger);
|
||||
}
|
||||
}
|
||||
.read {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
.none {
|
||||
padding-top: 5px;
|
||||
}
|
||||
.spinner-container {
|
||||
min-height: 2em;
|
||||
}
|
||||
.spinner {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-width: 2px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.show-all a {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 30px;
|
||||
color: var(--primary-med-or-secondary-high);
|
||||
background: var(--blend-primary-secondary-5);
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
background: var(--primary-low);
|
||||
}
|
||||
}
|
||||
/* as a big ol' click target, don't let text inside be selected */
|
||||
@include unselectable;
|
||||
}
|
||||
|
||||
.user-menu.show-avatars {
|
||||
// Styles to have user avatar positioned and sized correctly
|
||||
.user-menu.show-avatars,
|
||||
.user-notifications-list.show-avatars {
|
||||
li {
|
||||
a {
|
||||
.icon-avatar {
|
||||
|
@ -765,38 +765,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.large-notifications {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.large-notification {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.d-icon {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
span:first-child {
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
// Can remove this once other languages have removed html from i18n values
|
||||
div {
|
||||
.fa {
|
||||
display: none;
|
||||
}
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.second-factor {
|
||||
.second-factor-item {
|
||||
width: 100%;
|
||||
|
@ -46,7 +46,7 @@
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.time,
|
||||
.relative-date,
|
||||
.delete-info,
|
||||
.draft-type {
|
||||
line-height: var(--line-height-small);
|
||||
@ -69,9 +69,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
.notification .time {
|
||||
margin-left: auto;
|
||||
float: none;
|
||||
.user-notifications-list {
|
||||
padding-top: 0;
|
||||
|
||||
li.notification {
|
||||
padding: 0.25em 0;
|
||||
border-bottom: 1px solid var(--primary-low);
|
||||
|
||||
a {
|
||||
align-items: center;
|
||||
}
|
||||
.relative-date {
|
||||
margin-left: auto;
|
||||
padding-top: 0;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
&:not(.show-avatars) {
|
||||
li.notification {
|
||||
padding: 0.75em 0;
|
||||
|
||||
.d-icon {
|
||||
padding-top: 0;
|
||||
font-size: var(--font-up-2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.expand-item,
|
||||
@ -102,27 +125,6 @@
|
||||
float: right;
|
||||
}
|
||||
|
||||
.notification {
|
||||
li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
p {
|
||||
display: inline-block;
|
||||
|
||||
span:first-child {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
// common/base/header.scss
|
||||
.fa,
|
||||
.icon {
|
||||
color: var(--primary-med-or-secondary-med);
|
||||
font-size: var(--font-up-4);
|
||||
}
|
||||
}
|
||||
|
||||
.excerpt {
|
||||
margin: 1em 0 0 0;
|
||||
font-size: var(--font-0);
|
||||
|
@ -2,4 +2,3 @@
|
||||
@import "sidebar/edit-navigation-menu/tags-modal";
|
||||
@import "user-card";
|
||||
@import "user-info";
|
||||
@import "user-stream-item";
|
||||
|
@ -1,8 +0,0 @@
|
||||
// Desktop styles for "user-stream-item" component
|
||||
.user-stream {
|
||||
.notification {
|
||||
&.unread {
|
||||
background-color: var(--tertiary-low);
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,6 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.notification {
|
||||
&.unread {
|
||||
background-color: var(--tertiary-low);
|
||||
}
|
||||
}
|
||||
|
||||
.group-member-info {
|
||||
.name {
|
||||
vertical-align: inherit;
|
||||
|
Reference in New Issue
Block a user