From 66e9a77e36a67a5c29feb8be5b1e323b463e0468 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 15 Sep 2020 18:09:08 +0200 Subject: Display unread marker for notifications --- .../flavours/glitch/features/notifications/components/follow.js | 6 ++++-- .../glitch/features/notifications/components/follow_request.js | 6 ++++-- .../glitch/features/notifications/components/notification.js | 6 ++++++ app/javascript/flavours/glitch/features/notifications/index.js | 6 +++++- 4 files changed, 19 insertions(+), 5 deletions(-) (limited to 'app/javascript/flavours/glitch/features/notifications') diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow.js b/app/javascript/flavours/glitch/features/notifications/components/follow.js index 2b71f3107..0d3162fc9 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow.js @@ -5,6 +5,7 @@ import PropTypes from 'prop-types'; import { FormattedMessage } from 'react-intl'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; +import classNames from 'classnames'; // Our imports. import Permalink from 'flavours/glitch/components/permalink'; @@ -19,6 +20,7 @@ export default class NotificationFollow extends ImmutablePureComponent { id: PropTypes.string.isRequired, account: ImmutablePropTypes.map.isRequired, notification: ImmutablePropTypes.map.isRequired, + unread: PropTypes.bool, }; handleMoveUp = () => { @@ -59,7 +61,7 @@ export default class NotificationFollow extends ImmutablePureComponent { } render () { - const { account, notification, hidden } = this.props; + const { account, notification, hidden, unread } = this.props; // Links to the display name. const displayName = account.get('display_name_html') || account.get('username'); @@ -76,7 +78,7 @@ export default class NotificationFollow extends ImmutablePureComponent { // Renders. return ( -
+
diff --git a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js index d73dac434..f351c1035 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/follow_request.js +++ b/app/javascript/flavours/glitch/features/notifications/components/follow_request.js @@ -10,6 +10,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import NotificationOverlayContainer from '../containers/overlay_container'; import { HotKeys } from 'react-hotkeys'; import Icon from 'flavours/glitch/components/icon'; +import classNames from 'classnames'; const messages = defineMessages({ authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, @@ -25,6 +26,7 @@ class FollowRequest extends ImmutablePureComponent { onReject: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, notification: ImmutablePropTypes.map.isRequired, + unread: PropTypes.bool, }; handleMoveUp = () => { @@ -65,7 +67,7 @@ class FollowRequest extends ImmutablePureComponent { } render () { - const { intl, hidden, account, onAuthorize, onReject, notification } = this.props; + const { intl, hidden, account, onAuthorize, onReject, notification, unread } = this.props; if (!account) { return
; @@ -94,7 +96,7 @@ class FollowRequest extends ImmutablePureComponent { return ( -
+
diff --git a/app/javascript/flavours/glitch/features/notifications/components/notification.js b/app/javascript/flavours/glitch/features/notifications/components/notification.js index 62fc28386..7b80c2228 100644 --- a/app/javascript/flavours/glitch/features/notifications/components/notification.js +++ b/app/javascript/flavours/glitch/features/notifications/components/notification.js @@ -46,6 +46,7 @@ export default class Notification extends ImmutablePureComponent { onMoveDown={onMoveDown} onMoveUp={onMoveUp} onMention={onMention} + unread={this.props.unread} /> ); case 'follow_request': @@ -58,6 +59,7 @@ export default class Notification extends ImmutablePureComponent { onMoveDown={onMoveDown} onMoveUp={onMoveUp} onMention={onMention} + unread={this.props.unread} /> ); case 'mention': @@ -77,6 +79,7 @@ export default class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} onUnmount={this.props.onUnmount} withDismiss + unread={this.props.unread} /> ); case 'favourite': @@ -98,6 +101,7 @@ export default class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} onUnmount={this.props.onUnmount} withDismiss + unread={this.props.unread} /> ); case 'reblog': @@ -119,6 +123,7 @@ export default class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} onUnmount={this.props.onUnmount} withDismiss + unread={this.props.unread} /> ); case 'poll': @@ -140,6 +145,7 @@ export default class Notification extends ImmutablePureComponent { cacheMediaWidth={this.props.cacheMediaWidth} onUnmount={this.props.onUnmount} withDismiss + unread={this.props.unread} /> ); default: diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 26710feff..cef0a0d2a 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -24,6 +24,7 @@ import { debounce } from 'lodash'; import ScrollableList from 'flavours/glitch/components/scrollable_list'; import LoadGap from 'flavours/glitch/components/load_gap'; import Icon from 'flavours/glitch/components/icon'; +import compareId from 'flavours/glitch/util/compare_id'; import NotificationPurgeButtonsContainer from 'flavours/glitch/containers/notification_purge_buttons_container'; @@ -56,6 +57,7 @@ const mapStateToProps = state => ({ hasMore: state.getIn(['notifications', 'hasMore']), numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), + lastReadId: state.getIn(['notifications', 'lastReadId']), }); /* glitch */ @@ -93,6 +95,7 @@ class Notifications extends React.PureComponent { onEnterCleaningMode: PropTypes.func, onMount: PropTypes.func, onUnmount: PropTypes.func, + lastReadId: PropTypes.string, }; static defaultProps = { @@ -195,7 +198,7 @@ class Notifications extends React.PureComponent { } render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar } = this.props; + const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId } = this.props; const { notifCleaning, notifCleaningActive } = this.props; const { animatingNCD } = this.state; const pinned = !!columnId; @@ -224,6 +227,7 @@ class Notifications extends React.PureComponent { accountId={item.get('account')} onMoveUp={this.handleMoveUp} onMoveDown={this.handleMoveDown} + unread={lastReadId && compareId(item.get('id'), lastReadId) > 0} /> )); } else { -- cgit From 94c290d7d2179adeb0437c00b571f62c41c7fd12 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 15 Sep 2020 20:54:26 +0200 Subject: Only update read marker when giving focus/mounting column --- app/javascript/flavours/glitch/features/notifications/index.js | 4 ++-- app/javascript/flavours/glitch/reducers/notifications.js | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) (limited to 'app/javascript/flavours/glitch/features/notifications') diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index cef0a0d2a..1c8c68218 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -57,7 +57,7 @@ const mapStateToProps = state => ({ hasMore: state.getIn(['notifications', 'hasMore']), numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), - lastReadId: state.getIn(['notifications', 'lastReadId']), + lastReadId: state.getIn(['notifications', 'readMarkerId']), }); /* glitch */ @@ -227,7 +227,7 @@ class Notifications extends React.PureComponent { accountId={item.get('account')} onMoveUp={this.handleMoveUp} onMoveDown={this.handleMoveDown} - unread={lastReadId && compareId(item.get('id'), lastReadId) > 0} + unread={lastReadId !== '0' && compareId(item.get('id'), lastReadId) > 0} /> )); } else { diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 33f014dd1..7f6784eae 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -39,6 +39,7 @@ const initialState = ImmutableMap({ mounted: 0, unread: 0, lastReadId: '0', + readMarkerId: '0', isLoading: false, cleaningMode: false, isTabVisible: true, @@ -183,6 +184,7 @@ const deleteMarkedNotifs = (state) => { const updateMounted = (state) => { state = state.update('mounted', count => count + 1); if (!shouldCountUnreadNotifications(state)) { + state = state.set('readMarkerId', state.get('lastReadId')); state = clearUnread(state); } return state; @@ -191,6 +193,7 @@ const updateMounted = (state) => { const updateVisibility = (state, visibility) => { state = state.set('isTabVisible', visibility); if (!shouldCountUnreadNotifications(state)) { + state = state.set('readMarkerId', state.get('lastReadId')); state = clearUnread(state); } return state; @@ -212,6 +215,10 @@ const recountUnread = (state, last_read_id) => { mutable.set('lastReadId', last_read_id); } + if (compareId(last_read_id, mutable.get('readMarkerId')) > 0) { + mutable.set('readMarkerId', last_read_id); + } + if (state.get('unread') > 0 || shouldCountUnreadNotifications(state)) { mutable.set('unread', mutable.get('pendingItems').count(item => item !== null) + mutable.get('items').count(item => item && compareId(item.get('id'), last_read_id) > 0)); } -- cgit From f1c0cf98061eee5eb6130b167d033c851ee58f14 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 15 Sep 2020 23:42:58 +0200 Subject: Add button to manually mark all notifications as read --- .../flavours/glitch/actions/notifications.js | 8 ++++++ .../glitch/features/notifications/index.js | 32 ++++++++++++++++++++-- .../flavours/glitch/reducers/notifications.js | 4 +++ 3 files changed, 41 insertions(+), 3 deletions(-) (limited to 'app/javascript/flavours/glitch/features/notifications') diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index ceb1e6df6..b44469cf4 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -45,6 +45,8 @@ export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT'; export const NOTIFICATIONS_SET_VISIBILITY = 'NOTIFICATIONS_SET_VISIBILITY'; +export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ'; + defineMessages({ mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, }); @@ -318,3 +320,9 @@ export function setFilter (filterType) { dispatch(saveSettings()); }; }; + +export function markNotificationsAsRead() { + return { + type: NOTIFICATIONS_MARK_AS_READ, + }; +}; diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index 1c8c68218..d1a0640cb 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -12,6 +12,7 @@ import { mountNotifications, unmountNotifications, loadPending, + markNotificationsAsRead, } from 'flavours/glitch/actions/notifications'; import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns'; import NotificationContainer from './containers/notification_container'; @@ -31,6 +32,7 @@ import NotificationPurgeButtonsContainer from 'flavours/glitch/containers/notifi const messages = defineMessages({ title: { id: 'column.notifications', defaultMessage: 'Notifications' }, enterNotifCleaning : { id: 'notification_purge.start', defaultMessage: 'Enter notification cleaning mode' }, + markAsRead : { id: 'notifications.mark_as_read', defaultMessage: 'Mark every notification as read' }, }); const getNotifications = createSelector([ @@ -58,6 +60,7 @@ const mapStateToProps = state => ({ numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), lastReadId: state.getIn(['notifications', 'readMarkerId']), + canMarkAsRead: !state.getIn(['notifications', 'items']).isEmpty() && state.getIn(['notifications', 'readMarkerId']) !== '0' && compareId(state.getIn(['notifications', 'items']).first().get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0, }); /* glitch */ @@ -65,6 +68,9 @@ const mapDispatchToProps = dispatch => ({ onEnterCleaningMode(yes) { dispatch(enterNotificationClearingMode(yes)); }, + onMarkAsRead() { + dispatch(markNotificationsAsRead()); + }, onMount() { dispatch(mountNotifications()); }, @@ -96,6 +102,7 @@ class Notifications extends React.PureComponent { onMount: PropTypes.func, onUnmount: PropTypes.func, lastReadId: PropTypes.string, + canMarkAsRead: PropTypes.bool, }; static defaultProps = { @@ -197,8 +204,12 @@ class Notifications extends React.PureComponent { this.props.onEnterCleaningMode(!this.props.notifCleaningActive); } + handleMarkAsRead = () => { + this.props.onMarkAsRead(); + } + render () { - const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId } = this.props; + const { intl, notifications, shouldUpdateScroll, isLoading, isUnread, columnId, multiColumn, hasMore, numPending, showFilterBar, lastReadId, canMarkAsRead } = this.props; const { notifCleaning, notifCleaningActive } = this.props; const { animatingNCD } = this.state; const pinned = !!columnId; @@ -256,6 +267,21 @@ class Notifications extends React.PureComponent { ); + const extraButtons = []; + + if (canMarkAsRead) { + extraButtons.push( + + ); + } + const notifCleaningButtonClassName = classNames('column-header__button', { 'active': notifCleaningActive, }); @@ -267,7 +293,7 @@ class Notifications extends React.PureComponent { const msgEnterNotifCleaning = intl.formatMessage(messages.enterNotifCleaning); - const notifCleaningButton = ( + extraButtons.push(