diff options
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) => { |