add mark as seen to notifications
This commit is contained in:
parent
2ae3484363
commit
9ee4b256fb
15 changed files with 236 additions and 152 deletions
2
flow-typed/notification.js
vendored
2
flow-typed/notification.js
vendored
|
@ -7,6 +7,7 @@ declare type WebNotification = {
|
|||
is_device_notified: boolean,
|
||||
is_emailed: boolean,
|
||||
is_read: boolean,
|
||||
is_seen: boolean,
|
||||
notification_parameters: {
|
||||
device: {
|
||||
analytics_label: string,
|
||||
|
@ -23,6 +24,7 @@ declare type WebNotification = {
|
|||
comment_author: string,
|
||||
hash: string,
|
||||
claim_title: string,
|
||||
comment?: string,
|
||||
},
|
||||
email: {},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doSeeNotifications } from 'redux/actions/notifications';
|
||||
import Notification from './view';
|
||||
|
||||
export default connect()(Notification);
|
||||
export default connect(null, {
|
||||
doSeeNotifications,
|
||||
})(Notification);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
import { NOTIFICATION_CREATOR_SUBSCRIBER, NOTIFICATION_COMMENT } from 'constants/notifications';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import classnames from 'classnames';
|
||||
import Icon from 'component/common/icon';
|
||||
import DateTime from 'component/dateTime';
|
||||
import ChannelThumbnail from 'component/channelThumbnail';
|
||||
|
@ -14,58 +15,87 @@ type Props = {
|
|||
notification: WebNotification,
|
||||
menuButton: boolean,
|
||||
children: any,
|
||||
doSeeNotifications: ([number]) => void,
|
||||
};
|
||||
|
||||
export default function Notification(props: Props) {
|
||||
const { notification, menuButton = false } = props;
|
||||
const { notification, menuButton = false, doSeeNotifications } = props;
|
||||
const { push } = useHistory();
|
||||
const notificationTarget = notification && notification.notification_parameters.device.target;
|
||||
const { notification_rule, notification_parameters, is_seen, id } = notification;
|
||||
const notificationTarget = notification && notification_parameters.device.target;
|
||||
const commentText = notification_rule === NOTIFICATION_COMMENT && notification_parameters.dynamic.comment;
|
||||
let notificationLink = formatLbryUrlForWeb(notificationTarget);
|
||||
if (notification.notification_rule === NOTIFICATION_COMMENT && notification.notification_parameters.dynamic.hash) {
|
||||
notificationLink += `?lc=${notification.notification_parameters.dynamic.hash}`;
|
||||
if (notification_rule === NOTIFICATION_COMMENT && notification_parameters.dynamic.hash) {
|
||||
notificationLink += `?lc=${notification_parameters.dynamic.hash}`;
|
||||
}
|
||||
|
||||
let icon;
|
||||
switch (notification.notification_rule) {
|
||||
switch (notification_rule) {
|
||||
case NOTIFICATION_CREATOR_SUBSCRIBER:
|
||||
icon = <Icon icon={ICONS.SUBSCRIBE} sectionIcon className="notification__icon" />;
|
||||
break;
|
||||
case NOTIFICATION_COMMENT:
|
||||
icon = <ChannelThumbnail small uri={notification.notification_parameters.dynamic.comment_author} />;
|
||||
icon = <ChannelThumbnail small uri={notification_parameters.dynamic.comment_author} />;
|
||||
break;
|
||||
default:
|
||||
icon = <Icon icon={ICONS.NOTIFICATION} sectionIcon className="notification__icon" />;
|
||||
}
|
||||
|
||||
function handleNotificationClick() {
|
||||
if (!is_seen) {
|
||||
doSeeNotifications([id]);
|
||||
}
|
||||
|
||||
if (notificationLink) {
|
||||
push(notificationLink);
|
||||
}
|
||||
}
|
||||
|
||||
const Wrapper = menuButton
|
||||
? (props: { children: any }) => (
|
||||
<MenuItem className="menu__link--notification" onSelect={() => push(notificationLink)}>
|
||||
<MenuItem className="menu__link--notification" onSelect={handleNotificationClick}>
|
||||
{props.children}
|
||||
</MenuItem>
|
||||
)
|
||||
: (props: { children: any }) => (
|
||||
<a className="menu__link--notification" onClick={() => push(notificationLink)}>
|
||||
: notificationLink
|
||||
? (props: { children: any }) => (
|
||||
<a className="menu__link--notification" onClick={handleNotificationClick}>
|
||||
{props.children}
|
||||
</a>
|
||||
)
|
||||
: (props: { children: any }) => (
|
||||
<span
|
||||
className={is_seen ? 'menu__link--notification-nolink' : 'menu__link--notification'}
|
||||
onClick={handleNotificationClick}
|
||||
>
|
||||
{props.children}
|
||||
</span>
|
||||
);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<div className="notification__wrapper">
|
||||
<div
|
||||
className={classnames('notification__wrapper', {
|
||||
'notification__wrapper--unseen': !is_seen,
|
||||
})}
|
||||
>
|
||||
<div className="notification__icon">{icon}</div>
|
||||
<div className="notification__content">
|
||||
<div>
|
||||
{notification.notification_rule !== NOTIFICATION_COMMENT && (
|
||||
<div className="notification__title">{notification.notification_parameters.device.title}</div>
|
||||
{notification_rule !== NOTIFICATION_COMMENT && (
|
||||
<div className="notification__title">{notification_parameters.device.title}</div>
|
||||
)}
|
||||
|
||||
<div className="notification__text">
|
||||
{notification.notification_parameters.device.text.replace(
|
||||
// This is terrible and will be replaced when I make the comment channel clickable
|
||||
'commented on',
|
||||
notification.group_count ? `left ${notification.group_count} comments on` : 'commented on'
|
||||
)}
|
||||
</div>
|
||||
{notification_rule === NOTIFICATION_COMMENT && commentText ? (
|
||||
<>
|
||||
<div className="notification__title">{notification_parameters.device.title}</div>
|
||||
<div className="notification__text mobile-hidden">{commentText}</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="notification__text">{notification_parameters.device.text}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="notification__time">
|
||||
|
|
|
@ -3,28 +3,18 @@ import * as PAGES from 'constants/pages';
|
|||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import Icon from 'component/common/icon';
|
||||
import Notification from 'component/notification';
|
||||
import NotificationBubble from 'component/notificationBubble';
|
||||
import Button from 'component/button';
|
||||
import { useHistory } from 'react-router';
|
||||
// import { Menu, MenuList, MenuButton, MenuPopover, MenuItems, MenuItem } from '@reach/menu-button';
|
||||
|
||||
type Props = {
|
||||
unreadCount: number,
|
||||
fetching: boolean,
|
||||
notifications: ?Array<Notification>,
|
||||
doReadNotifications: () => void,
|
||||
user: ?User,
|
||||
};
|
||||
|
||||
export default function NotificationHeaderButton(props: Props) {
|
||||
const {
|
||||
unreadCount,
|
||||
// notifications,
|
||||
fetching,
|
||||
doReadNotifications,
|
||||
user,
|
||||
} = props;
|
||||
const { unreadCount, doReadNotifications, user } = props;
|
||||
const notificationsEnabled = user && user.experimental_ui;
|
||||
const { push } = useHistory();
|
||||
|
||||
|
@ -43,7 +33,6 @@ export default function NotificationHeaderButton(props: Props) {
|
|||
return (
|
||||
<Button
|
||||
onClick={handleMenuClick}
|
||||
disabled={fetching}
|
||||
aria-label={__('Notifications')}
|
||||
title={__('Notifications')}
|
||||
className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
|
@ -52,41 +41,4 @@ export default function NotificationHeaderButton(props: Props) {
|
|||
<NotificationBubble />
|
||||
</Button>
|
||||
);
|
||||
|
||||
// Below is disabled until scroll style issues are resolved
|
||||
// return (
|
||||
// <Menu>
|
||||
// <MenuButton
|
||||
// onClick={handleMenuClick}
|
||||
// disabled={fetching}
|
||||
// aria-label={__('Notifications')}
|
||||
// title={__('Notifications')}
|
||||
// className="header__navigation-item menu__title header__navigation-item--icon"
|
||||
// >
|
||||
// <Icon size={18} icon={ICONS.NOTIFICATION} aria-hidden />
|
||||
// {unreadCount > 0 && <span className="notification__bubble">{unreadCount}</span>}
|
||||
// </MenuButton>
|
||||
|
||||
// {notifications && notifications.length > 0 ? (
|
||||
// <MenuList className="menu__list--header">
|
||||
// {notifications.slice(0, 7).map((notification, index) => (
|
||||
// <Notification menuButton key={notification.id} id={notification.id} notification={notification} />
|
||||
// ))}
|
||||
|
||||
// <MenuItem className="menu__link" onSelect={() => push(`/$/${PAGES.NOTIFICATIONS}`)}>
|
||||
// <Icon aria-hidden icon={ICONS.NOTIFICATION} />
|
||||
// {__('View All')}
|
||||
// </MenuItem>
|
||||
// </MenuList>
|
||||
// ) : (
|
||||
// <MenuPopover>
|
||||
// <div className="menu__list--header notifications__empty">No notifications yet.</div>
|
||||
// {/* Below is needed because MenuPopover isn't meant to be used this way */}
|
||||
// <MenuItems>
|
||||
// <MenuItem disabled onSelect={() => {}} />
|
||||
// </MenuItems>
|
||||
// </MenuPopover>
|
||||
// )}
|
||||
// </Menu>
|
||||
// );
|
||||
}
|
||||
|
|
|
@ -239,6 +239,9 @@ export const NOTIFICATION_LIST_FAILED = 'NOTIFICATION_LIST_FAILED';
|
|||
export const NOTIFICATION_READ_STARTED = 'NOTIFICATION_READ_STARTED';
|
||||
export const NOTIFICATION_READ_COMPLETED = 'NOTIFICATION_READ_COMPLETED';
|
||||
export const NOTIFICATION_READ_FAILED = 'NOTIFICATION_READ_FAILED';
|
||||
export const NOTIFICATION_SEEN_STARTED = 'NOTIFICATION_SEEN_STARTED';
|
||||
export const NOTIFICATION_SEEN_COMPLETED = 'NOTIFICATION_SEEN_COMPLETED';
|
||||
export const NOTIFICATION_SEEN_FAILED = 'NOTIFICATION_SEEN_FAILED';
|
||||
export const CREATE_TOAST = 'CREATE_TOAST';
|
||||
export const DISMISS_TOAST = 'DISMISS_TOAST';
|
||||
export const CREATE_ERROR = 'CREATE_ERROR';
|
||||
|
|
|
@ -3,16 +3,20 @@ import {
|
|||
selectNotifications,
|
||||
selectIsFetchingNotifications,
|
||||
selectUnreadNotificationCount,
|
||||
selectUnseenNotificationCount,
|
||||
} from 'redux/selectors/notifications';
|
||||
import { doReadNotifications } from 'redux/actions/notifications';
|
||||
import { doReadNotifications, doNotificationList, doSeeAllNotifications } from 'redux/actions/notifications';
|
||||
import NotificationsPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
notifications: selectNotifications(state),
|
||||
fetching: selectIsFetchingNotifications(state),
|
||||
unreadCount: selectUnreadNotificationCount(state),
|
||||
unseenCount: selectUnseenNotificationCount(state),
|
||||
});
|
||||
|
||||
export default connect(select, {
|
||||
doReadNotifications,
|
||||
doNotificationList,
|
||||
doSeeAllNotifications,
|
||||
})(NotificationsPage);
|
||||
|
|
|
@ -1,68 +1,28 @@
|
|||
// @flow
|
||||
import * as ICONS from 'constants/icons';
|
||||
import { NOTIFICATION_COMMENT } from 'constants/notifications';
|
||||
import React from 'react';
|
||||
import Page from 'component/page';
|
||||
import Card from 'component/common/card';
|
||||
import Spinner from 'component/spinner';
|
||||
import Notification from 'component/notification';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import Button from 'component/button';
|
||||
import Yrbl from 'component/yrbl';
|
||||
import usePrevious from 'effects/use-previous';
|
||||
|
||||
type Props = {
|
||||
notifications: ?Array<Notification>,
|
||||
notifications: Array<Notification>,
|
||||
fetching: boolean,
|
||||
unreadCount: number,
|
||||
unseenCount: number,
|
||||
doSeeAllNotifications: () => void,
|
||||
doReadNotifications: () => void,
|
||||
};
|
||||
|
||||
export default function NotificationsPage(props: Props) {
|
||||
const { notifications, fetching, unreadCount, doReadNotifications } = props;
|
||||
|
||||
// Group sequential comment notifications if they are by the same author
|
||||
let groupedCount = 1;
|
||||
const groupedNotifications =
|
||||
notifications &&
|
||||
notifications.reduce((list, notification, index) => {
|
||||
if (index === 0) {
|
||||
return [notification];
|
||||
}
|
||||
|
||||
const previousNotification = notifications[index - 1];
|
||||
const isCommentNotification = notification.notification_rule === NOTIFICATION_COMMENT;
|
||||
const previousIsCommentNotification = previousNotification.notification_rule === NOTIFICATION_COMMENT;
|
||||
if (isCommentNotification && previousIsCommentNotification) {
|
||||
const notificationTarget = notification.notification_parameters.device.target;
|
||||
const previousTarget = previousNotification && previousNotification.notification_parameters.device.target;
|
||||
const author = notification.notification_parameters.dynamic.comment_author;
|
||||
const previousAuthor = previousNotification.notification_parameters.dynamic.comment_author;
|
||||
|
||||
if (author === previousAuthor && notificationTarget === previousTarget) {
|
||||
const newList = [...list];
|
||||
newList.pop();
|
||||
groupedCount += 1;
|
||||
const newNotification = {
|
||||
...previousNotification,
|
||||
group_count: groupedCount,
|
||||
};
|
||||
|
||||
newList[index - groupedCount] = newNotification;
|
||||
return newList;
|
||||
} else {
|
||||
if (groupedCount > 1) {
|
||||
groupedCount = 1;
|
||||
}
|
||||
|
||||
return [...list, notification];
|
||||
}
|
||||
} else {
|
||||
if (groupedCount > 1) {
|
||||
groupedCount = 1;
|
||||
}
|
||||
|
||||
return [...list, notification];
|
||||
}
|
||||
}, []);
|
||||
const { notifications, fetching, unreadCount, unseenCount, doSeeAllNotifications, doReadNotifications } = props;
|
||||
const [hasFetched, setHasFetched] = React.useState(false);
|
||||
const previousFetching = usePrevious(fetching);
|
||||
const hasNotifications = notifications.length > 0;
|
||||
|
||||
React.useEffect(() => {
|
||||
if (unreadCount > 0) {
|
||||
|
@ -70,43 +30,64 @@ export default function NotificationsPage(props: Props) {
|
|||
}
|
||||
}, [unreadCount, doReadNotifications]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if ((fetching === false && previousFetching === true) || hasNotifications) {
|
||||
setHasFetched(true);
|
||||
}
|
||||
}, [fetching, previousFetching, setHasFetched, hasNotifications]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
{fetching && (
|
||||
{fetching && !hasNotifications && (
|
||||
<div className="main--empty">
|
||||
<Spinner delayed />
|
||||
</div>
|
||||
)}
|
||||
{groupedNotifications && groupedNotifications.length > 0 ? (
|
||||
<Card
|
||||
isBodyList
|
||||
title={__('Notifications')}
|
||||
body={
|
||||
<div className="notification_list">
|
||||
{groupedNotifications.map((notification, index) => {
|
||||
if (!notification) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <Notification key={notification.id} notification={notification} />;
|
||||
})}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
<Yrbl
|
||||
title={__('No Notifications')}
|
||||
subtitle={
|
||||
<div>
|
||||
<p>{__("You don't have any notifications yet, but they will be here when you do!")}</p>
|
||||
<div className="section__actions">
|
||||
<Button button="primary" icon={ICONS.HOME} label={__('Go Home')} navigate="/" />
|
||||
{hasFetched && (
|
||||
<>
|
||||
{notifications && notifications.length > 0 ? (
|
||||
<Card
|
||||
isBodyList
|
||||
title={
|
||||
<span>
|
||||
{__('Notifications')}
|
||||
{fetching && <Spinner type="small" />}
|
||||
</span>
|
||||
}
|
||||
titleActions={
|
||||
unseenCount > 0 && (
|
||||
<Button
|
||||
icon={ICONS.EYE}
|
||||
onClick={doSeeAllNotifications}
|
||||
button="secondary"
|
||||
label={__('Mark all as read')}
|
||||
/>
|
||||
)
|
||||
}
|
||||
body={
|
||||
<div className="notification_list">
|
||||
{notifications.map((notification, index) => {
|
||||
return <Notification key={notification.id} notification={notification} />;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
<Yrbl
|
||||
title={__('No Notifications')}
|
||||
subtitle={
|
||||
<div>
|
||||
<p>{__("You don't have any notifications yet, but they will be here when you do!")}</p>
|
||||
<div className="section__actions">
|
||||
<Button button="primary" icon={ICONS.HOME} label={__('Go Home')} navigate="/" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -3,6 +3,7 @@ import * as ACTIONS from 'constants/action_types';
|
|||
import { Lbryio } from 'lbryinc';
|
||||
import uuid from 'uuid/v4';
|
||||
import { selectNotifications } from 'redux/selectors/notifications';
|
||||
import { doResolveUris } from 'lbry-redux';
|
||||
|
||||
export function doToast(params: ToastParams) {
|
||||
if (!params) {
|
||||
|
@ -45,6 +46,20 @@ export function doNotificationList() {
|
|||
return Lbryio.call('notification', 'list')
|
||||
.then(response => {
|
||||
const notifications = response || [];
|
||||
const channelsToResolve = notifications
|
||||
.filter((notification: WebNotification) => {
|
||||
if (
|
||||
notification.notification_parameters.dynamic &&
|
||||
notification.notification_parameters.dynamic.comment_author
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.map(notification => notification.notification_parameters.dynamic.comment_author);
|
||||
|
||||
dispatch(doResolveUris(channelsToResolve));
|
||||
dispatch({ type: ACTIONS.NOTIFICATION_LIST_COMPLETED, data: { notifications } });
|
||||
})
|
||||
.catch(error => {
|
||||
|
@ -74,3 +89,32 @@ export function doReadNotifications() {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSeeNotifications(notificationIds: Array<string>) {
|
||||
return (dispatch: Dispatch) => {
|
||||
dispatch({ type: ACTIONS.NOTIFICATION_SEEN_STARTED });
|
||||
return Lbryio.call('notification', 'edit', { notification_ids: notificationIds.join(','), is_seen: true })
|
||||
.then(() => {
|
||||
dispatch({
|
||||
type: ACTIONS.NOTIFICATION_SEEN_COMPLETED,
|
||||
data: {
|
||||
notificationIds,
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
dispatch({ type: ACTIONS.NOTIFICATION_SEEN_FAILED, data: { error } });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function doSeeAllNotifications() {
|
||||
return (dispatch: Dispatch, getState: GetState) => {
|
||||
const state = getState();
|
||||
const notifications = selectNotifications(state);
|
||||
const unSeenNotifications =
|
||||
notifications && notifications.filter(notification => !notification.is_seen).map(notification => notification.id);
|
||||
|
||||
dispatch(doSeeNotifications(unSeenNotifications));
|
||||
};
|
||||
}
|
||||
|
|
|
@ -66,6 +66,22 @@ export default handleActions(
|
|||
...state,
|
||||
};
|
||||
},
|
||||
[ACTIONS.NOTIFICATION_SEEN_COMPLETED]: (state, action) => {
|
||||
const { notifications } = state;
|
||||
const { notificationIds } = action.data;
|
||||
const newNotifications = notifications.map(notification => {
|
||||
if (notificationIds.includes(notification.id)) {
|
||||
return { ...notification, is_seen: true };
|
||||
}
|
||||
|
||||
return notification;
|
||||
});
|
||||
|
||||
return {
|
||||
...state,
|
||||
notifications: newNotifications,
|
||||
};
|
||||
},
|
||||
|
||||
// Errors
|
||||
[ACTIONS.CREATE_ERROR]: (state: NotificationState, action: DoError) => {
|
||||
|
|
|
@ -10,6 +10,10 @@ export const selectUnreadNotificationCount = createSelector(selectNotifications,
|
|||
return notifications ? notifications.filter(notification => !notification.is_read).length : 0;
|
||||
});
|
||||
|
||||
export const selectUnseenNotificationCount = createSelector(selectNotifications, notifications => {
|
||||
return notifications ? notifications.filter(notification => !notification.is_seen).length : 0;
|
||||
});
|
||||
|
||||
export const selectToast = createSelector(selectState, state => {
|
||||
if (state.toasts.length) {
|
||||
const { id, params } = state.toasts[0];
|
||||
|
|
|
@ -213,6 +213,10 @@
|
|||
& > *:not(:last-child) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.card__media--nsfw {
|
||||
|
|
|
@ -26,6 +26,26 @@
|
|||
.channel-thumbnail {
|
||||
@include handleChannelGif(3rem);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
.channel-thumbnail {
|
||||
@include handleChannelGif(2rem);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification__wrapper--unseen {
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-card-background-highlighted);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--color-button-secondary-bg);
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
padding: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
.notification__content {
|
||||
|
@ -33,23 +53,36 @@
|
|||
flex: 1;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
.notification__title {
|
||||
font-size: var(--font-small);
|
||||
font-weight: bold;
|
||||
color: var(--color-text);
|
||||
margin-bottom: var(--spacing-s);
|
||||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification__text {
|
||||
font-size: var(--font-body);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification__time {
|
||||
@extend .help;
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
margin-left: var(--spacing-s);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.notification__bubble {
|
||||
|
|
|
@ -108,6 +108,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
.menu__link--notification-nolink {
|
||||
@extend .menu__link--notification;
|
||||
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.menu__link--all-notifications {
|
||||
@extend .button--alt;
|
||||
width: auto;
|
||||
|
|
|
@ -97,6 +97,6 @@ $breakpoint-large: 1600px;
|
|||
|
||||
@media (max-width: $breakpoint-small) {
|
||||
:root {
|
||||
--font-base: 16px;
|
||||
--font-body: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
--color-placeholder-background: #f0f0f0;
|
||||
--color-header-background: #ffffff;
|
||||
--color-card-background: #ffffff;
|
||||
--color-card-background-highlighted: #f6faff;
|
||||
--color-card-background-highlighted: #f0f7ff;
|
||||
--color-list-header: #fff;
|
||||
--color-file-viewer-background: var(--color-card-background);
|
||||
--color-tabs-background: var(--color-card-background);
|
||||
|
|
Loading…
Reference in a new issue