about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-10-30 15:06:43 +0100
committerEugen Rochko <eugen@zeonfederated.com>2016-10-30 15:06:43 +0100
commite8ff4c8e56650bf061c63a7da3d84b742e618b6a (patch)
treecb9aa48393bc6108655db7490434ae29d3145ee5 /app
parent7060bdf04bde59aab9addce95f00d6e1075a62ba (diff)
Refactoring redux state into different reducers
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/components/actions/accounts.jsx13
-rw-r--r--app/assets/javascripts/components/actions/statuses.jsx54
-rw-r--r--app/assets/javascripts/components/actions/timelines.jsx26
-rw-r--r--app/assets/javascripts/components/components/status.jsx2
-rw-r--r--app/assets/javascripts/components/containers/status_container.jsx2
-rw-r--r--app/assets/javascripts/components/features/account/index.jsx2
-rw-r--r--app/assets/javascripts/components/features/account_timeline/index.jsx2
-rw-r--r--app/assets/javascripts/components/features/compose/containers/suggestions_container.jsx2
-rw-r--r--app/assets/javascripts/components/features/followers/containers/account_container.jsx2
-rw-r--r--app/assets/javascripts/components/features/status/index.jsx10
-rw-r--r--app/assets/javascripts/components/features/ui/containers/navigation_container.jsx2
-rw-r--r--app/assets/javascripts/components/reducers/accounts.jsx80
-rw-r--r--app/assets/javascripts/components/reducers/compose.jsx6
-rw-r--r--app/assets/javascripts/components/reducers/follow.jsx2
-rw-r--r--app/assets/javascripts/components/reducers/index.jsx8
-rw-r--r--app/assets/javascripts/components/reducers/meta.jsx7
-rw-r--r--app/assets/javascripts/components/reducers/notifications.jsx4
-rw-r--r--app/assets/javascripts/components/reducers/relationships.jsx34
-rw-r--r--app/assets/javascripts/components/reducers/statuses.jsx68
-rw-r--r--app/assets/javascripts/components/reducers/suggestions.jsx13
-rw-r--r--app/assets/javascripts/components/reducers/timelines.jsx208
-rw-r--r--app/assets/javascripts/components/reducers/user_lists.jsx10
-rw-r--r--app/assets/javascripts/components/selectors/index.jsx10
23 files changed, 348 insertions, 219 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index 224562199..fdfd204a1 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -1,5 +1,4 @@
 import api       from '../api'
-import axios     from 'axios';
 import Immutable from 'immutable';
 
 export const ACCOUNT_SET_SELF = 'ACCOUNT_SET_SELF';
@@ -53,12 +52,11 @@ export function setAccountSelf(account) {
 
 export function fetchAccount(id) {
   return (dispatch, getState) => {
-    const boundApi = api(getState);
-
     dispatch(fetchAccountRequest(id));
 
-    axios.all([boundApi.get(`/api/v1/accounts/${id}`), boundApi.get(`/api/v1/accounts/relationships?id=${id}`)]).then(values => {
-      dispatch(fetchAccountSuccess(values[0].data, values[1].data[0]));
+    api(getState).get(`/api/v1/accounts/${id}`).then(response => {
+      dispatch(fetchAccountSuccess(response.data));
+      dispatch(fetchRelationships([id]));
     }).catch(error => {
       dispatch(fetchAccountFail(id, error));
     });
@@ -107,11 +105,10 @@ export function fetchAccountRequest(id) {
   };
 };
 
-export function fetchAccountSuccess(account, relationship) {
+export function fetchAccountSuccess(account) {
   return {
     type: ACCOUNT_FETCH_SUCCESS,
-    account: account,
-    relationship: relationship
+    account: account
   };
 };
 
diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx
index 2fb2d1ba1..cbee94bca 100644
--- a/app/assets/javascripts/components/actions/statuses.jsx
+++ b/app/assets/javascripts/components/actions/statuses.jsx
@@ -1,5 +1,6 @@
-import api   from '../api';
-import axios from 'axios';
+import api from '../api';
+
+import { deleteFromTimelines } from './timelines';
 
 export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
 export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
@@ -9,6 +10,10 @@ export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST';
 export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS';
 export const STATUS_DELETE_FAIL    = 'STATUS_DELETE_FAIL';
 
+export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST';
+export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS';
+export const CONTEXT_FETCH_FAIL    = 'CONTEXT_FETCH_FAIL';
+
 export function fetchStatusRequest(id) {
   return {
     type: STATUS_FETCH_REQUEST,
@@ -18,12 +23,11 @@ export function fetchStatusRequest(id) {
 
 export function fetchStatus(id) {
   return (dispatch, getState) => {
-    const boundApi = api(getState);
-
     dispatch(fetchStatusRequest(id));
 
-    axios.all([boundApi.get(`/api/v1/statuses/${id}`), boundApi.get(`/api/v1/statuses/${id}/context`)]).then(values => {
-      dispatch(fetchStatusSuccess(values[0].data, values[1].data));
+    api(getState).get(`/api/v1/statuses/${id}`).then(response => {
+      dispatch(fetchStatusSuccess(response.data));
+      dispatch(fetchContext(id));
     }).catch(error => {
       dispatch(fetchStatusFail(id, error));
     });
@@ -52,6 +56,7 @@ export function deleteStatus(id) {
 
     api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
       dispatch(deleteStatusSuccess(id));
+      dispatch(deleteFromTimelines(id));
     }).catch(error => {
       dispatch(deleteStatusFail(id, error));
     });
@@ -79,3 +84,40 @@ export function deleteStatusFail(id, error) {
     error: error
   };
 };
+
+export function fetchContext(id) {
+  return (dispatch, getState) => {
+    dispatch(fetchContextRequest(id));
+
+    api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
+      dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
+    }).catch(error => {
+      dispatch(fetchContextFail(id, error));
+    });
+  };
+};
+
+export function fetchContextRequest(id) {
+  return {
+    type: CONTEXT_FETCH_REQUEST,
+    id
+  };
+};
+
+export function fetchContextSuccess(id, ancestors, descendants) {
+  return {
+    type: CONTEXT_FETCH_SUCCESS,
+    id,
+    ancestors,
+    descendants,
+    statuses: ancestors.concat(descendants)
+  };
+};
+
+export function fetchContextFail(id, error) {
+  return {
+    type: CONTEXT_FETCH_FAIL,
+    id,
+    error
+  };
+};
diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx
index 831065feb..01eee1712 100644
--- a/app/assets/javascripts/components/actions/timelines.jsx
+++ b/app/assets/javascripts/components/actions/timelines.jsx
@@ -21,17 +21,29 @@ export function refreshTimelineSuccess(timeline, statuses, replace) {
 };
 
 export function updateTimeline(timeline, status) {
-  return {
-    type: TIMELINE_UPDATE,
-    timeline: timeline,
-    status: status
+  return (dispatch, getState) => {
+    const references = status.reblog ? getState().get('statuses').filter((item, itemId) => (itemId === status.reblog.id || item.get('reblog') === status.reblog.id)).map((_, itemId) => itemId) : [];
+
+    dispatch({
+      type: TIMELINE_UPDATE,
+      timeline,
+      status,
+      references
+    });
   };
 };
 
 export function deleteFromTimelines(id) {
-  return {
-    type: TIMELINE_DELETE,
-    id: id
+  return (dispatch, getState) => {
+    const accountId  = getState().getIn(['statuses', id, 'account']);
+    const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
+
+    dispatch({
+      type: TIMELINE_DELETE,
+      id,
+      accountId,
+      references
+    });
   };
 };
 
diff --git a/app/assets/javascripts/components/components/status.jsx b/app/assets/javascripts/components/components/status.jsx
index 1d1560003..8424023a6 100644
--- a/app/assets/javascripts/components/components/status.jsx
+++ b/app/assets/javascripts/components/components/status.jsx
@@ -15,7 +15,7 @@ const Status = React.createClass({
   },
 
   propTypes: {
-    status: ImmutablePropTypes.map.isRequired,
+    status: ImmutablePropTypes.map,
     wrapped: React.PropTypes.bool,
     onReply: React.PropTypes.func,
     onFavourite: React.PropTypes.func,
diff --git a/app/assets/javascripts/components/containers/status_container.jsx b/app/assets/javascripts/components/containers/status_container.jsx
index 0f4b8ee16..5e0b489b3 100644
--- a/app/assets/javascripts/components/containers/status_container.jsx
+++ b/app/assets/javascripts/components/containers/status_container.jsx
@@ -19,7 +19,7 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     status: getStatus(state, props.id),
-    me: state.getIn(['timelines', 'me'])
+    me: state.getIn(['meta', 'me'])
   });
 
   return mapStateToProps;
diff --git a/app/assets/javascripts/components/features/account/index.jsx b/app/assets/javascripts/components/features/account/index.jsx
index 548f7fc1f..6cadcff4d 100644
--- a/app/assets/javascripts/components/features/account/index.jsx
+++ b/app/assets/javascripts/components/features/account/index.jsx
@@ -26,7 +26,7 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     account: getAccount(state, Number(props.params.accountId)),
-    me: state.getIn(['timelines', 'me'])
+    me: state.getIn(['meta', 'me'])
   });
 
   return mapStateToProps;
diff --git a/app/assets/javascripts/components/features/account_timeline/index.jsx b/app/assets/javascripts/components/features/account_timeline/index.jsx
index f79570361..cae88efdb 100644
--- a/app/assets/javascripts/components/features/account_timeline/index.jsx
+++ b/app/assets/javascripts/components/features/account_timeline/index.jsx
@@ -10,7 +10,7 @@ import LoadingIndicator       from '../../components/loading_indicator';
 
 const mapStateToProps = (state, props) => ({
   statusIds: state.getIn(['timelines', 'accounts_timelines', Number(props.params.accountId)]),
-  me: state.getIn(['timelines', 'me'])
+  me: state.getIn(['meta', 'me'])
 });
 
 const AccountTimeline = React.createClass({
diff --git a/app/assets/javascripts/components/features/compose/containers/suggestions_container.jsx b/app/assets/javascripts/components/features/compose/containers/suggestions_container.jsx
index 12ee1ebc2..944ceed85 100644
--- a/app/assets/javascripts/components/features/compose/containers/suggestions_container.jsx
+++ b/app/assets/javascripts/components/features/compose/containers/suggestions_container.jsx
@@ -2,7 +2,7 @@ import { connect }           from 'react-redux';
 import SuggestionsBox        from '../components/suggestions_box';
 
 const mapStateToProps = (state) => ({
-  accountIds: state.get('suggestions')
+  accountIds: state.getIn(['user_lists', 'suggestions'])
 });
 
 export default connect(mapStateToProps)(SuggestionsBox);
diff --git a/app/assets/javascripts/components/features/followers/containers/account_container.jsx b/app/assets/javascripts/components/features/followers/containers/account_container.jsx
index 988d60adb..c5d5c5881 100644
--- a/app/assets/javascripts/components/features/followers/containers/account_container.jsx
+++ b/app/assets/javascripts/components/features/followers/containers/account_container.jsx
@@ -11,7 +11,7 @@ const makeMapStateToProps = () => {
 
   const mapStateToProps = (state, props) => ({
     account: getAccount(state, props.id),
-    me: state.getIn(['timelines', 'me'])
+    me: state.getIn(['meta', 'me'])
   });
 
   return mapStateToProps;
diff --git a/app/assets/javascripts/components/features/status/index.jsx b/app/assets/javascripts/components/features/status/index.jsx
index dc29a87c7..78498039c 100644
--- a/app/assets/javascripts/components/features/status/index.jsx
+++ b/app/assets/javascripts/components/features/status/index.jsx
@@ -31,7 +31,7 @@ const makeMapStateToProps = () => {
     status: getStatus(state, Number(props.params.statusId)),
     ancestorsIds: state.getIn(['timelines', 'ancestors', Number(props.params.statusId)]),
     descendantsIds: state.getIn(['timelines', 'descendants', Number(props.params.statusId)]),
-    me: state.getIn(['timelines', 'me'])
+    me: state.getIn(['meta', 'me'])
   });
 
   return mapStateToProps;
@@ -43,8 +43,8 @@ const Status = React.createClass({
     params: React.PropTypes.object.isRequired,
     dispatch: React.PropTypes.func.isRequired,
     status: ImmutablePropTypes.map,
-    ancestorsIds: ImmutablePropTypes.orderedSet,
-    descendantsIds: ImmutablePropTypes.orderedSet
+    ancestorsIds: ImmutablePropTypes.list,
+    descendantsIds: ImmutablePropTypes.list
   },
 
   mixins: [PureRenderMixin],
@@ -101,11 +101,11 @@ const Status = React.createClass({
 
     const account = status.get('account');
 
-    if (ancestorsIds) {
+    if (ancestorsIds && ancestorsIds.size > 0) {
       ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
     }
 
-    if (descendantsIds) {
+    if (descendantsIds && descendantsIds.size > 0) {
       descendants = <div>{this.renderChildren(descendantsIds)}</div>;
     }
 
diff --git a/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx
index 4aeea4c37..51e2513d8 100644
--- a/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx
+++ b/app/assets/javascripts/components/features/ui/containers/navigation_container.jsx
@@ -2,7 +2,7 @@ import { connect }   from 'react-redux';
 import NavigationBar from '../components/navigation_bar';
 
 const mapStateToProps = (state, props) => ({
-  account: state.getIn(['timelines', 'accounts', state.getIn(['timelines', 'me'])])
+  account: state.getIn(['accounts', state.getIn(['meta', 'me'])])
 });
 
 export default connect(mapStateToProps)(NavigationBar);
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx
new file mode 100644
index 000000000..95f8059d1
--- /dev/null
+++ b/app/assets/javascripts/components/reducers/accounts.jsx
@@ -0,0 +1,80 @@
+import {
+  ACCOUNT_SET_SELF,
+  ACCOUNT_FETCH_SUCCESS,
+  FOLLOWERS_FETCH_SUCCESS,
+  FOLLOWING_FETCH_SUCCESS,
+  ACCOUNT_TIMELINE_FETCH_SUCCESS,
+  ACCOUNT_TIMELINE_EXPAND_SUCCESS
+} from '../actions/accounts';
+import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
+import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
+import {
+  REBLOG_SUCCESS,
+  UNREBLOG_SUCCESS,
+  FAVOURITE_SUCCESS,
+  UNFAVOURITE_SUCCESS
+} from '../actions/interactions';
+import {
+  TIMELINE_REFRESH_SUCCESS,
+  TIMELINE_UPDATE,
+  TIMELINE_EXPAND_SUCCESS
+} from '../actions/timelines';
+import { STATUS_FETCH_SUCCESS } from '../actions/statuses';
+import Immutable from 'immutable';
+
+const normalizeAccount = (state, account) => state.set(account.get('id'), account);
+
+const normalizeAccounts = (state, accounts) => {
+  accounts.forEach(account => {
+    state = normalizeAccount(state, account);
+  });
+
+  return state;
+};
+
+const normalizeAccountFromStatus = (state, status) => {
+  state = normalizeAccount(state, status.get('account'));
+
+  if (status.getIn(['reblog', 'account'])) {
+    state = normalizeAccount(state, status.getIn(['reblog', 'account']));
+  }
+
+  return state;
+};
+
+const normalizeAccountsFromStatuses = (state, statuses) => {
+  statuses.forEach(status => {
+    state = normalizeAccountFromStatus(state, status);
+  });
+
+  return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function accounts(state = initialState, action) {
+  switch(action.type) {
+    case ACCOUNT_SET_SELF:
+    case ACCOUNT_FETCH_SUCCESS:
+    case FOLLOW_SUBMIT_SUCCESS:
+      return normalizeAccount(state, Immutable.fromJS(action.account));
+    case SUGGESTIONS_FETCH_SUCCESS:
+    case FOLLOWERS_FETCH_SUCCESS:
+    case FOLLOWING_FETCH_SUCCESS:
+      return normalizeAccounts(state, Immutable.fromJS(action.accounts));
+    case TIMELINE_REFRESH_SUCCESS:
+    case TIMELINE_EXPAND_SUCCESS:
+    case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+    case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+      return normalizeAccountsFromStatuses(state, Immutable.fromJS(action.statuses));
+    case TIMELINE_UPDATE:
+    case REBLOG_SUCCESS:
+    case FAVOURITE_SUCCESS:
+    case UNREBLOG_SUCCESS:
+    case UNFAVOURITE_SUCCESS:
+    case STATUS_FETCH_SUCCESS:
+      return normalizeAccountFromStatus(state, Immutable.fromJS(action.status));
+    default:
+      return state;
+  }
+};
diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx
index f2fd3ad80..1c676326f 100644
--- a/app/assets/javascripts/components/reducers/compose.jsx
+++ b/app/assets/javascripts/components/reducers/compose.jsx
@@ -11,10 +11,10 @@ import {
   COMPOSE_UPLOAD_FAIL,
   COMPOSE_UPLOAD_UNDO,
   COMPOSE_UPLOAD_PROGRESS
-}                           from '../actions/compose';
-import { TIMELINE_DELETE }  from '../actions/timelines';
+} from '../actions/compose';
+import { TIMELINE_DELETE } from '../actions/timelines';
 import { ACCOUNT_SET_SELF } from '../actions/accounts';
-import Immutable            from 'immutable';
+import Immutable from 'immutable';
 
 const initialState = Immutable.Map({
   text: '',
diff --git a/app/assets/javascripts/components/reducers/follow.jsx b/app/assets/javascripts/components/reducers/follow.jsx
index e1dc293c6..ed6e8e0ef 100644
--- a/app/assets/javascripts/components/reducers/follow.jsx
+++ b/app/assets/javascripts/components/reducers/follow.jsx
@@ -3,7 +3,7 @@ import {
   FOLLOW_SUBMIT_REQUEST,
   FOLLOW_SUBMIT_SUCCESS,
   FOLLOW_SUBMIT_FAIL
-}                from '../actions/follow';
+} from '../actions/follow';
 import Immutable from 'immutable';
 
 const initialState = Immutable.Map({
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx
index 62d6839d7..ccc9e8e8e 100644
--- a/app/assets/javascripts/components/reducers/index.jsx
+++ b/app/assets/javascripts/components/reducers/index.jsx
@@ -7,7 +7,9 @@ import notifications         from './notifications';
 import { loadingBarReducer } from 'react-redux-loading-bar';
 import modal                 from './modal';
 import user_lists            from './user_lists';
-import suggestions           from './suggestions';
+import accounts              from './accounts';
+import statuses              from './statuses';
+import relationships         from './relationships';
 
 export default combineReducers({
   timelines,
@@ -18,5 +20,7 @@ export default combineReducers({
   loadingBar: loadingBarReducer,
   modal,
   user_lists,
-  suggestions
+  accounts,
+  statuses,
+  relationships
 });
diff --git a/app/assets/javascripts/components/reducers/meta.jsx b/app/assets/javascripts/components/reducers/meta.jsx
index 71a14dbd3..c7222c60b 100644
--- a/app/assets/javascripts/components/reducers/meta.jsx
+++ b/app/assets/javascripts/components/reducers/meta.jsx
@@ -1,5 +1,6 @@
-import { ACCESS_TOKEN_SET }         from '../actions/meta';
-import Immutable                    from 'immutable';
+import { ACCESS_TOKEN_SET } from '../actions/meta';
+import { ACCOUNT_SET_SELF } from '../actions/accounts';
+import Immutable from 'immutable';
 
 const initialState = Immutable.Map();
 
@@ -7,6 +8,8 @@ export default function meta(state = initialState, action) {
   switch(action.type) {
     case ACCESS_TOKEN_SET:
       return state.set('access_token', action.token);
+    case ACCOUNT_SET_SELF:
+      return state.set('me', action.account.id);
     default:
       return state;
   }
diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx
index 886587bdb..efe8d9739 100644
--- a/app/assets/javascripts/components/reducers/notifications.jsx
+++ b/app/assets/javascripts/components/reducers/notifications.jsx
@@ -2,8 +2,8 @@ import {
   NOTIFICATION_SHOW,
   NOTIFICATION_DISMISS,
   NOTIFICATION_CLEAR
-}                            from '../actions/notifications';
-import Immutable             from 'immutable';
+} from '../actions/notifications';
+import Immutable from 'immutable';
 
 const initialState = Immutable.List([]);
 
diff --git a/app/assets/javascripts/components/reducers/relationships.jsx b/app/assets/javascripts/components/reducers/relationships.jsx
new file mode 100644
index 000000000..165b1f3ff
--- /dev/null
+++ b/app/assets/javascripts/components/reducers/relationships.jsx
@@ -0,0 +1,34 @@
+import {
+  ACCOUNT_FOLLOW_SUCCESS,
+  ACCOUNT_UNFOLLOW_SUCCESS,
+  ACCOUNT_BLOCK_SUCCESS,
+  ACCOUNT_UNBLOCK_SUCCESS,
+  RELATIONSHIPS_FETCH_SUCCESS
+} from '../actions/accounts';
+import Immutable from 'immutable';
+
+const normalizeRelationship = (state, relationship) => state.set(relationship.get('id'), relationship);
+
+const normalizeRelationships = (state, relationships) => {
+  relationships.forEach(relationship => {
+    state = normalizeRelationship(state, relationship);
+  });
+
+  return state;
+};
+
+const initialState = Immutable.Map();
+
+export default function relationships(state = initialState, action) {
+  switch(action.type) {
+    case ACCOUNT_FOLLOW_SUCCESS:
+    case ACCOUNT_UNFOLLOW_SUCCESS:
+    case ACCOUNT_BLOCK_SUCCESS:
+    case ACCOUNT_UNBLOCK_SUCCESS:
+      return normalizeRelationship(state, Immutable.fromJS(action.relationship));
+    case RELATIONSHIPS_FETCH_SUCCESS:
+      return normalizeRelationships(state, Immutable.fromJS(action.relationships));
+    default:
+      return state;
+  }
+};
diff --git a/app/assets/javascripts/components/reducers/statuses.jsx b/app/assets/javascripts/components/reducers/statuses.jsx
new file mode 100644
index 000000000..4a970038f
--- /dev/null
+++ b/app/assets/javascripts/components/reducers/statuses.jsx
@@ -0,0 +1,68 @@
+import {
+  REBLOG_SUCCESS,
+  UNREBLOG_SUCCESS,
+  FAVOURITE_SUCCESS,
+  UNFAVOURITE_SUCCESS
+} from '../actions/interactions';
+import {
+  STATUS_FETCH_SUCCESS,
+  CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import {
+  TIMELINE_REFRESH_SUCCESS,
+  TIMELINE_UPDATE,
+  TIMELINE_DELETE,
+  TIMELINE_EXPAND_SUCCESS
+} from '../actions/timelines';
+import {
+  ACCOUNT_TIMELINE_FETCH_SUCCESS,
+  ACCOUNT_TIMELINE_EXPAND_SUCCESS
+} from '../actions/accounts';
+import Immutable from 'immutable';
+
+const normalizeStatus = (state, status) => {
+  status = status.set('account', status.getIn(['account', 'id']));
+
+  if (status.getIn(['reblog', 'id'])) {
+    state  = normalizeStatus(state, status.get('reblog'));
+    status = status.set('reblog', status.getIn(['reblog', 'id']));
+  }
+
+  return state.set(status.get('id'), status);
+};
+
+const normalizeStatuses = (state, statuses) => {
+  statuses.forEach(status => {
+    state = normalizeStatus(state, status);
+  });
+
+  return state;
+};
+
+const deleteStatus = (state, id, references) => {
+  references.forEach(ref => {
+    state = deleteStatus(state, ref[0], []);
+  });
+
+  return state.delete(id);
+};
+
+const initialState = Immutable.Map();
+
+export default function statuses(state = initialState, action) {
+  switch(action.type) {
+    case TIMELINE_UPDATE:
+    case STATUS_FETCH_SUCCESS:
+      return normalizeStatus(state, Immutable.fromJS(action.status));
+    case TIMELINE_REFRESH_SUCCESS:
+    case TIMELINE_EXPAND_SUCCESS:
+    case ACCOUNT_TIMELINE_FETCH_SUCCESS:
+    case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
+    case CONTEXT_FETCH_SUCCESS:
+      return normalizeStatuses(state, Immutable.fromJS(action.statuses));
+    case TIMELINE_DELETE:
+      return deleteStatus(state, action.id, action.references);
+    default:
+      return state;
+  }
+};
diff --git a/app/assets/javascripts/components/reducers/suggestions.jsx b/app/assets/javascripts/components/reducers/suggestions.jsx
deleted file mode 100644
index 9d2b7d96a..000000000
--- a/app/assets/javascripts/components/reducers/suggestions.jsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
-import Immutable                     from 'immutable';
-
-const initialState = Immutable.List();
-
-export default function suggestions(state = initialState, action) {
-  switch(action.type) {
-    case SUGGESTIONS_FETCH_SUCCESS:
-      return Immutable.List(action.accounts.map(item => item.id));
-    default:
-      return state;
-  }
-}
diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx
index 4bf97c18d..db13cad31 100644
--- a/app/assets/javascripts/components/reducers/timelines.jsx
+++ b/app/assets/javascripts/components/reducers/timelines.jsx
@@ -3,85 +3,52 @@ import {
   TIMELINE_UPDATE,
   TIMELINE_DELETE,
   TIMELINE_EXPAND_SUCCESS
-}                                from '../actions/timelines';
+} from '../actions/timelines';
 import {
   REBLOG_SUCCESS,
   UNREBLOG_SUCCESS,
   FAVOURITE_SUCCESS,
   UNFAVOURITE_SUCCESS
-}                                from '../actions/interactions';
+} from '../actions/interactions';
 import {
-  ACCOUNT_SET_SELF,
   ACCOUNT_FETCH_SUCCESS,
-  ACCOUNT_FOLLOW_SUCCESS,
-  ACCOUNT_UNFOLLOW_SUCCESS,
-  ACCOUNT_BLOCK_SUCCESS,
-  ACCOUNT_UNBLOCK_SUCCESS,
   ACCOUNT_TIMELINE_FETCH_SUCCESS,
-  ACCOUNT_TIMELINE_EXPAND_SUCCESS,
-  FOLLOWERS_FETCH_SUCCESS,
-  FOLLOWING_FETCH_SUCCESS,
-  RELATIONSHIPS_FETCH_SUCCESS
-}                                from '../actions/accounts';
+  ACCOUNT_TIMELINE_EXPAND_SUCCESS
+} from '../actions/accounts';
 import {
   STATUS_FETCH_SUCCESS,
-  STATUS_DELETE_SUCCESS
-}                                from '../actions/statuses';
-import { FOLLOW_SUBMIT_SUCCESS } from '../actions/follow';
-import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
-import Immutable                 from 'immutable';
+  CONTEXT_FETCH_SUCCESS
+} from '../actions/statuses';
+import Immutable from 'immutable';
 
 const initialState = Immutable.Map({
-  home: Immutable.List([]),
-  mentions: Immutable.List([]),
-  public: Immutable.List([]),
-  statuses: Immutable.Map(),
-  accounts: Immutable.Map(),
+  home: Immutable.List(),
+  mentions: Immutable.List(),
+  public: Immutable.List(),
   accounts_timelines: Immutable.Map(),
-  me: null,
   ancestors: Immutable.Map(),
-  descendants: Immutable.Map(),
-  relationships: Immutable.Map(),
-  suggestions: Immutable.List([])
+  descendants: Immutable.Map()
 });
 
-function normalizeStatus(state, status) {
-  // Separate account
-  let account = status.get('account');
-  status = status.set('account', account.get('id'));
+const normalizeStatus = (state, status) => {
+  const replyToId = status.get('in_reply_to_id');
+  const id        = status.get('id');
 
-  // Separate reblog, repeat for reblog
-  let reblog = status.get('reblog', null);
-
-  if (reblog !== null) {
-    status = status.set('reblog', reblog.get('id'));
-    state  = normalizeStatus(state, reblog);
-  }
-
-  // Replies
-  if (status.get('in_reply_to_id')) {
-    state = state.updateIn(['descendants', status.get('in_reply_to_id')], set => {
-      if (!Immutable.OrderedSet.isOrderedSet(set)) {
-        return Immutable.OrderedSet([status.get('id')]);
-      } else {
-        return set.add(status.get('id'));
-      }
-    });
-  }
+  if (replyToId) {
+    if (!state.getIn(['descendants', replyToId], Immutable.List()).includes(id)) {
+      state = state.updateIn(['descendants', replyToId], Immutable.List(), set => set.push(id));
+    }
 
-  return state.withMutations(map => {
-    if (status.get('in_reply_to_id')) {
-      map.updateIn(['descendants', status.get('in_reply_to_id')], Immutable.OrderedSet(), set => set.add(status.get('id')));
-      map.updateIn(['ancestors', status.get('id')], Immutable.OrderedSet(), set => set.add(status.get('in_reply_to_id')));
+    if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
+      state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
     }
+  }
 
-    map.setIn(['accounts', account.get('id')], account);
-    map.setIn(['statuses', status.get('id')], status);
-  });
+  return state;
 };
 
-function normalizeTimeline(state, timeline, statuses, replace = false) {
-  let ids = Immutable.List([]);
+const normalizeTimeline = (state, timeline, statuses, replace = false) => {
+  let ids = Immutable.List();
 
   statuses.forEach((status, i) => {
     state = normalizeStatus(state, status);
@@ -91,8 +58,8 @@ function normalizeTimeline(state, timeline, statuses, replace = false) {
   return state.update(timeline, list => (replace ? ids : list.unshift(...ids)));
 };
 
-function appendNormalizedTimeline(state, timeline, statuses) {
-  let moreIds = Immutable.List([]);
+const appendNormalizedTimeline = (state, timeline, statuses) => {
+  let moreIds = Immutable.List();
 
   statuses.forEach((status, i) => {
     state   = normalizeStatus(state, status);
@@ -102,8 +69,8 @@ function appendNormalizedTimeline(state, timeline, statuses) {
   return state.update(timeline, list => list.push(...moreIds));
 };
 
-function normalizeAccountTimeline(state, accountId, statuses, replace = false) {
-  let ids = Immutable.List([]);
+const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
+  let ids = Immutable.List();
 
   statuses.forEach((status, i) => {
     state = normalizeStatus(state, status);
@@ -113,7 +80,7 @@ function normalizeAccountTimeline(state, accountId, statuses, replace = false) {
   return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => (replace ? ids : list.unshift(...ids)));
 };
 
-function appendNormalizedAccountTimeline(state, accountId, statuses) {
+const appendNormalizedAccountTimeline = (state, accountId, statuses) => {
   let moreIds = Immutable.List([]);
 
   statuses.forEach((status, i) => {
@@ -124,107 +91,60 @@ function appendNormalizedAccountTimeline(state, accountId, statuses) {
   return state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.push(...moreIds));
 };
 
-function updateTimeline(state, timeline, status) {
+const updateTimeline = (state, timeline, status, references) => {
   state = normalizeStatus(state, status);
 
   state = state.update(timeline, list => {
     const reblogOfId = status.getIn(['reblog', 'id'], null);
 
     if (reblogOfId !== null) {
-      const otherReblogs = state.get('statuses').filter(item => item.get('reblog') === reblogOfId).map((_, itemId) => itemId);
-      list = list.filterNot(itemId => (itemId === reblogOfId || otherReblogs.includes(itemId)));
+      list = list.filterNot(itemId => references.includes(itemId));
     }
 
     return list.unshift(status.get('id'));
   });
 
-  //state = state.updateIn(['accounts_timelines', status.getIn(['account', 'id'])], Immutable.List([]), list => (list.includes(status.get('id')) ? list : list.unshift(status.get('id'))));
-
   return state;
 };
 
-function deleteStatus(state, id) {
-  const status = state.getIn(['statuses', id]);
-
-  if (!status) {
-    return state;
-  }
-
+const deleteStatus = (state, id, accountId, references) => {
   // Remove references from timelines
-  ['home', 'mentions'].forEach(function (timeline) {
+  ['home', 'mentions', 'public'].forEach(function (timeline) {
     state = state.update(timeline, list => list.filterNot(item => item === id));
   });
 
   // Remove references from account timelines
-  state = state.updateIn(['accounts_timelines', status.get('account')], Immutable.List([]), list => list.filterNot(item => item === id));
+  state = state.updateIn(['accounts_timelines', accountId], Immutable.List([]), list => list.filterNot(item => item === id));
 
-  // Remove reblogs of deleted status
-  const references = state.get('statuses').filter(item => item.get('reblog') === id);
-
-  references.forEach(referencingId => {
-    state = deleteStatus(state, referencingId);
+  // Remove references from context
+  state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
+    state = state.updateIn(['ancestors', descendantId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
   });
 
-  // Remove normalized status
-  return state.deleteIn(['statuses', id]);
-};
-
-function normalizeAccount(state, account, relationship) {
-  if (relationship) {
-    state = normalizeRelationship(state, relationship);
-  }
-
-  return state.setIn(['accounts', account.get('id')], account);
-};
-
-function normalizeRelationship(state, relationship) {
-  if (state.get('suggestions').includes(relationship.get('id')) && (relationship.get('following') || relationship.get('blocking'))) {
-    state = state.update('suggestions', list => list.filterNot(id => id === relationship.get('id')));
-  }
+  state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
+    state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
+  });
 
-  return state.setIn(['relationships', relationship.get('id')], relationship);
-};
+  state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
 
-function normalizeRelationships(state, relationships) {
-  relationships.forEach(relationship => {
-    state = normalizeRelationship(state, relationship);
+  // Remove reblogs of deleted status
+  references.forEach(ref => {
+    state = deleteStatus(state, ref[0], ref[1], []);
   });
 
   return state;
 };
 
-function setSelf(state, account) {
-  state = normalizeAccount(state, account);
-  return state.set('me', account.get('id'));
-};
-
-function normalizeContext(state, status, ancestors, descendants) {
-  state = normalizeStatus(state, status);
-
-  let ancestorsIds = ancestors.map(ancestor => {
-    state = normalizeStatus(state, ancestor);
-    return ancestor.get('id');
-  }).toOrderedSet();
-
-  let descendantsIds = descendants.map(descendant => {
-    state = normalizeStatus(state, descendant);
-    return descendant.get('id');
-  }).toOrderedSet();
+const normalizeContext = (state, id, ancestors, descendants) => {
+  const ancestorsIds   = ancestors.map(ancestor => ancestor.get('id'));
+  const descendantsIds = descendants.map(descendant => descendant.get('id'));
 
   return state.withMutations(map => {
-    map.setIn(['ancestors', status.get('id')], ancestorsIds);
-    map.setIn(['descendants', status.get('id')], descendantsIds);
+    map.setIn(['ancestors', id], ancestorsIds);
+    map.setIn(['descendants', id], descendantsIds);
   });
 };
 
-function normalizeAccounts(state, accounts) {
-  accounts.forEach(account => {
-    state = state.setIn(['accounts', account.get('id')], account);
-  });
-
-  return state;
-};
-
 export default function timelines(state = initialState, action) {
   switch(action.type) {
     case TIMELINE_REFRESH_SUCCESS:
@@ -232,37 +152,15 @@ export default function timelines(state = initialState, action) {
     case TIMELINE_EXPAND_SUCCESS:
       return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses));
     case TIMELINE_UPDATE:
-      return updateTimeline(state, action.timeline, Immutable.fromJS(action.status));
+      return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
     case TIMELINE_DELETE:
-    case STATUS_DELETE_SUCCESS:
-      return deleteStatus(state, action.id);
-    case REBLOG_SUCCESS:
-    case FAVOURITE_SUCCESS:
-    case UNREBLOG_SUCCESS:
-    case UNFAVOURITE_SUCCESS:
-      return normalizeStatus(state, Immutable.fromJS(action.response));
-    case ACCOUNT_SET_SELF:
-      return setSelf(state, Immutable.fromJS(action.account));
-    case ACCOUNT_FETCH_SUCCESS:
-    case FOLLOW_SUBMIT_SUCCESS:
-      return normalizeAccount(state, Immutable.fromJS(action.account), Immutable.fromJS(action.relationship));
-    case ACCOUNT_FOLLOW_SUCCESS:
-    case ACCOUNT_UNFOLLOW_SUCCESS:
-    case ACCOUNT_UNBLOCK_SUCCESS:
-    case ACCOUNT_BLOCK_SUCCESS:
-      return normalizeRelationship(state, Immutable.fromJS(action.relationship));
-    case STATUS_FETCH_SUCCESS:
-      return normalizeContext(state, Immutable.fromJS(action.status), Immutable.fromJS(action.context.ancestors), Immutable.fromJS(action.context.descendants));
+      return deleteStatus(state, action.id, action.accountId, action.references);
+    case CONTEXT_FETCH_SUCCESS:
+      return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
     case ACCOUNT_TIMELINE_FETCH_SUCCESS:
       return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
     case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
       return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses));
-    case SUGGESTIONS_FETCH_SUCCESS:
-    case FOLLOWERS_FETCH_SUCCESS:
-    case FOLLOWING_FETCH_SUCCESS:
-      return normalizeAccounts(state, Immutable.fromJS(action.accounts));
-    case RELATIONSHIPS_FETCH_SUCCESS:
-      return normalizeRelationships(state, Immutable.fromJS(action.relationships));
     default:
       return state;
   }
diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx
index ee4b84296..d3b073e95 100644
--- a/app/assets/javascripts/components/reducers/user_lists.jsx
+++ b/app/assets/javascripts/components/reducers/user_lists.jsx
@@ -1,12 +1,14 @@
 import {
   FOLLOWERS_FETCH_SUCCESS,
   FOLLOWING_FETCH_SUCCESS
-}                          from '../actions/accounts';
-import Immutable           from 'immutable';
+} from '../actions/accounts';
+import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions';
+import Immutable from 'immutable';
 
 const initialState = Immutable.Map({
   followers: Immutable.Map(),
-  following: Immutable.Map()
+  following: Immutable.Map(),
+  suggestions: Immutable.List()
 });
 
 export default function userLists(state = initialState, action) {
@@ -15,6 +17,8 @@ export default function userLists(state = initialState, action) {
       return state.setIn(['followers', action.id], Immutable.List(action.accounts.map(item => item.id)));
     case FOLLOWING_FETCH_SUCCESS:
       return state.setIn(['following', action.id], Immutable.List(action.accounts.map(item => item.id)));
+    case SUGGESTIONS_FETCH_SUCCESS:
+      return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id)));
     default:
       return state;
   }
diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx
index 2bbbd3f70..33b179cb8 100644
--- a/app/assets/javascripts/components/selectors/index.jsx
+++ b/app/assets/javascripts/components/selectors/index.jsx
@@ -1,11 +1,11 @@
 import { createSelector } from 'reselect'
 import Immutable          from 'immutable';
 
-const getStatuses = state => state.getIn(['timelines', 'statuses']);
-const getAccounts = state => state.getIn(['timelines', 'accounts']);
+const getStatuses = state => state.get('statuses');
+const getAccounts = state => state.get('accounts');
 
-const getAccountBase         = (state, id) => state.getIn(['timelines', 'accounts', id], null);
-const getAccountRelationship = (state, id) => state.getIn(['timelines', 'relationships', id]);
+const getAccountBase         = (state, id) => state.getIn(['accounts', id], null);
+const getAccountRelationship = (state, id) => state.getIn(['relationships', id]);
 
 export const makeGetAccount = () => {
   return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => {
@@ -17,7 +17,7 @@ export const makeGetAccount = () => {
   });
 };
 
-const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null);
+const getStatusBase = (state, id) => state.getIn(['statuses', id], null);
 
 export const makeGetStatus = () => {
   return createSelector([getStatusBase, getStatuses, getAccounts], (base, statuses, accounts) => {