about summary refs log tree commit diff
path: root/app/javascript
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2018-10-19 01:47:29 +0200
committerGitHub <noreply@github.com>2018-10-19 01:47:29 +0200
commita38a452481d0f5207bb27ba7a2707c0028d2ac18 (patch)
tree5dfe4cab0fd6ebe15c924bd83e3abb6efca210db /app/javascript
parentbebe8ec887ba67c51353e09d7758819b117bf62d (diff)
Add unread indicator to conversations (#9009)
Diffstat (limited to 'app/javascript')
-rw-r--r--app/javascript/mastodon/actions/conversations.js11
-rw-r--r--app/javascript/mastodon/features/direct_timeline/components/conversation.js14
-rw-r--r--app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js8
-rw-r--r--app/javascript/mastodon/reducers/conversations.js10
-rw-r--r--app/javascript/styles/mastodon/components.scss5
5 files changed, 44 insertions, 4 deletions
diff --git a/app/javascript/mastodon/actions/conversations.js b/app/javascript/mastodon/actions/conversations.js
index cab05c1ba..aefd2fef7 100644
--- a/app/javascript/mastodon/actions/conversations.js
+++ b/app/javascript/mastodon/actions/conversations.js
@@ -13,6 +13,8 @@ export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS';
 export const CONVERSATIONS_FETCH_FAIL    = 'CONVERSATIONS_FETCH_FAIL';
 export const CONVERSATIONS_UPDATE        = 'CONVERSATIONS_UPDATE';
 
+export const CONVERSATIONS_READ = 'CONVERSATIONS_READ';
+
 export const mountConversations = () => ({
   type: CONVERSATIONS_MOUNT,
 });
@@ -21,6 +23,15 @@ export const unmountConversations = () => ({
   type: CONVERSATIONS_UNMOUNT,
 });
 
+export const markConversationRead = conversationId => (dispatch, getState) => {
+  dispatch({
+    type: CONVERSATIONS_READ,
+    id: conversationId,
+  });
+
+  api(getState).post(`/api/v1/conversations/${conversationId}/read`);
+};
+
 export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
   dispatch(expandConversationsRequest());
 
diff --git a/app/javascript/mastodon/features/direct_timeline/components/conversation.js b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
index f9a8d4f72..52e33c3c8 100644
--- a/app/javascript/mastodon/features/direct_timeline/components/conversation.js
+++ b/app/javascript/mastodon/features/direct_timeline/components/conversation.js
@@ -8,6 +8,7 @@ import DisplayName from '../../../components/display_name';
 import Avatar from '../../../components/avatar';
 import AttachmentList from '../../../components/attachment_list';
 import { HotKeys } from 'react-hotkeys';
+import classNames from 'classnames';
 
 export default class Conversation extends ImmutablePureComponent {
 
@@ -19,8 +20,10 @@ export default class Conversation extends ImmutablePureComponent {
     conversationId: PropTypes.string.isRequired,
     accounts: ImmutablePropTypes.list.isRequired,
     lastStatus: ImmutablePropTypes.map.isRequired,
+    unread:PropTypes.bool.isRequired,
     onMoveUp: PropTypes.func,
     onMoveDown: PropTypes.func,
+    markRead: PropTypes.func.isRequired,
   };
 
   handleClick = () => {
@@ -28,7 +31,12 @@ export default class Conversation extends ImmutablePureComponent {
       return;
     }
 
-    const { lastStatus } = this.props;
+    const { lastStatus, unread, markRead } = this.props;
+
+    if (unread) {
+      markRead();
+    }
+
     this.context.router.history.push(`/statuses/${lastStatus.get('id')}`);
   }
 
@@ -41,7 +49,7 @@ export default class Conversation extends ImmutablePureComponent {
   }
 
   render () {
-    const { accounts, lastStatus, lastAccount } = this.props;
+    const { accounts, lastStatus, lastAccount, unread } = this.props;
 
     if (lastStatus === null) {
       return null;
@@ -61,7 +69,7 @@ export default class Conversation extends ImmutablePureComponent {
 
     return (
       <HotKeys handlers={handlers}>
-        <div className='conversation focusable' tabIndex='0' onClick={this.handleClick} role='button'>
+        <div className={classNames('conversation', 'focusable', { 'conversation--unread': unread })} tabIndex='0' onClick={this.handleClick} role='button'>
           <div className='conversation__header'>
             <div className='conversation__avatars'>
               <div>{accounts.map(account => <Avatar key={account.get('id')} size={36} account={account} />)}</div>
diff --git a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
index 4166ee2ac..e2e2e3afb 100644
--- a/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
+++ b/app/javascript/mastodon/features/direct_timeline/containers/conversation_container.js
@@ -1,5 +1,6 @@
 import { connect } from 'react-redux';
 import Conversation from '../components/conversation';
+import { markConversationRead } from '../../../actions/conversations';
 
 const mapStateToProps = (state, { conversationId }) => {
   const conversation = state.getIn(['conversations', 'items']).find(x => x.get('id') === conversationId);
@@ -7,9 +8,14 @@ const mapStateToProps = (state, { conversationId }) => {
 
   return {
     accounts: conversation.get('accounts').map(accountId => state.getIn(['accounts', accountId], null)),
+    unread: conversation.get('unread'),
     lastStatus,
     lastAccount: lastStatus === null ? null : state.getIn(['accounts', lastStatus.get('account')], null),
   };
 };
 
-export default connect(mapStateToProps)(Conversation);
+const mapDispatchToProps = (dispatch, { conversationId }) => ({
+  markRead: () => dispatch(markConversationRead(conversationId)),
+});
+
+export default connect(mapStateToProps, mapDispatchToProps)(Conversation);
diff --git a/app/javascript/mastodon/reducers/conversations.js b/app/javascript/mastodon/reducers/conversations.js
index 6b3f22d25..ea39fccee 100644
--- a/app/javascript/mastodon/reducers/conversations.js
+++ b/app/javascript/mastodon/reducers/conversations.js
@@ -6,6 +6,7 @@ import {
   CONVERSATIONS_FETCH_SUCCESS,
   CONVERSATIONS_FETCH_FAIL,
   CONVERSATIONS_UPDATE,
+  CONVERSATIONS_READ,
 } from '../actions/conversations';
 import compareId from '../compare_id';
 
@@ -18,6 +19,7 @@ const initialState = ImmutableMap({
 
 const conversationToMap = item => ImmutableMap({
   id: item.id,
+  unread: item.unread,
   accounts: ImmutableList(item.accounts.map(a => a.id)),
   last_status: item.last_status.id,
 });
@@ -80,6 +82,14 @@ export default function conversations(state = initialState, action) {
     return state.update('mounted', count => count + 1);
   case CONVERSATIONS_UNMOUNT:
     return state.update('mounted', count => count - 1);
+  case CONVERSATIONS_READ:
+    return state.update('items', list => list.map(item => {
+      if (item.get('id') === action.id) {
+        return item.set('unread', false);
+      }
+
+      return item;
+    }));
   default:
     return state;
   }
diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss
index 129bde856..24b614a37 100644
--- a/app/javascript/styles/mastodon/components.scss
+++ b/app/javascript/styles/mastodon/components.scss
@@ -5503,6 +5503,11 @@ noscript {
   border-bottom: 1px solid lighten($ui-base-color, 8%);
   cursor: pointer;
 
+  &--unread {
+    background: lighten($ui-base-color, 8%);
+    border-bottom-color: lighten($ui-base-color, 12%);
+  }
+
   &__header {
     display: flex;
     margin-bottom: 15px;