Replace sprockets/browserify with Webpack (#2617)

* Replace browserify with webpack

* Add react-intl-translations-manager

* Do not minify in development, add offline-plugin for ServiceWorker background cache updates

* Adjust tests and dependencies

* Fix production deployments

* Fix tests

* More optimizations

* Improve travis cache for npm stuff

* Re-run travis

* Add back support for custom.scss as before

* Remove offline-plugin and babili

* Fix issue with Immutable.List().unshift(...values) not working as expected

* Make travis load schema instead of running all migrations in sequence

* Fix missing React import in WarningContainer. Optimize rendering performance by using ImmutablePureComponent instead of
React.PureComponent. ImmutablePureComponent uses Immutable.is() to compare props. Replace dynamic callback bindings in
<UI />

* Add react definitions to places that use JSX

* Add Procfile.dev for running rails, webpack and streaming API at the same time
This commit is contained in:
Eugen Rochko 2017-05-03 02:04:16 +02:00 committed by GitHub
parent 26bc591572
commit f5bf5ebb82
343 changed files with 5299 additions and 2081 deletions

View file

@ -0,0 +1,133 @@
import {
ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_EXPAND_SUCCESS,
ACCOUNT_TIMELINE_FETCH_SUCCESS,
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS
} from '../actions/accounts';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS
} from '../actions/mutes';
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS
} from '../actions/interactions';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_EXPAND_SUCCESS
} from '../actions/timelines';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS
} from '../actions/statuses';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS
} from '../actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS
} from '../actions/favourites';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const normalizeAccount = (state, account) => {
account = { ...account };
delete account.followers_count;
delete account.following_count;
delete account.statuses_count;
return state.set(account.id, Immutable.fromJS(account))
};
const normalizeAccounts = (state, accounts) => {
accounts.forEach(account => {
state = normalizeAccount(state, account);
});
return state;
};
const normalizeAccountFromStatus = (state, status) => {
state = normalizeAccount(state, status.account);
if (status.reblog && status.reblog.account) {
state = normalizeAccount(state, status.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 STORE_HYDRATE:
return state.merge(action.state.get('accounts'));
case ACCOUNT_FETCH_SUCCESS:
case NOTIFICATIONS_UPDATE:
return normalizeAccount(state, action.account);
case FOLLOWERS_FETCH_SUCCESS:
case FOLLOWERS_EXPAND_SUCCESS:
case FOLLOWING_FETCH_SUCCESS:
case FOLLOWING_EXPAND_SUCCESS:
case REBLOGS_FETCH_SUCCESS:
case FAVOURITES_FETCH_SUCCESS:
case COMPOSE_SUGGESTIONS_READY:
case FOLLOW_REQUESTS_FETCH_SUCCESS:
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
case BLOCKS_FETCH_SUCCESS:
case BLOCKS_EXPAND_SUCCESS:
case MUTES_FETCH_SUCCESS:
case MUTES_EXPAND_SUCCESS:
return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
case SEARCH_FETCH_SUCCESS:
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
case CONTEXT_FETCH_SUCCESS:
case FAVOURITED_STATUSES_FETCH_SUCCESS:
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
return normalizeAccountsFromStatuses(state, action.statuses);
case REBLOG_SUCCESS:
case FAVOURITE_SUCCESS:
case UNREBLOG_SUCCESS:
case UNFAVOURITE_SUCCESS:
return normalizeAccountFromStatus(state, action.response);
case TIMELINE_UPDATE:
case STATUS_FETCH_SUCCESS:
return normalizeAccountFromStatus(state, action.status);
default:
return state;
}
};

View file

@ -0,0 +1,135 @@
import {
ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_EXPAND_SUCCESS,
ACCOUNT_TIMELINE_FETCH_SUCCESS,
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
ACCOUNT_FOLLOW_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS
} from '../actions/accounts';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS
} from '../actions/mutes';
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS
} from '../actions/interactions';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_EXPAND_SUCCESS
} from '../actions/timelines';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS
} from '../actions/statuses';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS
} from '../actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS
} from '../actions/favourites';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS({
followers_count: account.followers_count,
following_count: account.following_count,
statuses_count: account.statuses_count,
}));
const normalizeAccounts = (state, accounts) => {
accounts.forEach(account => {
state = normalizeAccount(state, account);
});
return state;
};
const normalizeAccountFromStatus = (state, status) => {
state = normalizeAccount(state, status.account);
if (status.reblog && status.reblog.account) {
state = normalizeAccount(state, status.reblog.account);
}
return state;
};
const normalizeAccountsFromStatuses = (state, statuses) => {
statuses.forEach(status => {
state = normalizeAccountFromStatus(state, status);
});
return state;
};
const initialState = Immutable.Map();
export default function accountsCounters(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('accounts_counters'));
case ACCOUNT_FETCH_SUCCESS:
case NOTIFICATIONS_UPDATE:
return normalizeAccount(state, action.account);
case FOLLOWERS_FETCH_SUCCESS:
case FOLLOWERS_EXPAND_SUCCESS:
case FOLLOWING_FETCH_SUCCESS:
case FOLLOWING_EXPAND_SUCCESS:
case REBLOGS_FETCH_SUCCESS:
case FAVOURITES_FETCH_SUCCESS:
case COMPOSE_SUGGESTIONS_READY:
case FOLLOW_REQUESTS_FETCH_SUCCESS:
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
case BLOCKS_FETCH_SUCCESS:
case BLOCKS_EXPAND_SUCCESS:
case MUTES_FETCH_SUCCESS:
case MUTES_EXPAND_SUCCESS:
return normalizeAccounts(state, action.accounts);
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
case SEARCH_FETCH_SUCCESS:
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
case CONTEXT_FETCH_SUCCESS:
case FAVOURITED_STATUSES_FETCH_SUCCESS:
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
return normalizeAccountsFromStatuses(state, action.statuses);
case REBLOG_SUCCESS:
case FAVOURITE_SUCCESS:
case UNREBLOG_SUCCESS:
case UNFAVOURITE_SUCCESS:
return normalizeAccountFromStatus(state, action.response);
case TIMELINE_UPDATE:
case STATUS_FETCH_SUCCESS:
return normalizeAccountFromStatus(state, action.status);
case ACCOUNT_FOLLOW_SUCCESS:
return state.updateIn([action.relationship.id, 'followers_count'], num => num + 1);
case ACCOUNT_UNFOLLOW_SUCCESS:
return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
default:
return state;
}
};

View file

@ -0,0 +1,25 @@
import {
ALERT_SHOW,
ALERT_DISMISS,
ALERT_CLEAR
} from '../actions/alerts';
import Immutable from 'immutable';
const initialState = Immutable.List([]);
export default function alerts(state = initialState, action) {
switch(action.type) {
case ALERT_SHOW:
return state.push(Immutable.Map({
key: state.size > 0 ? state.last().get('key') + 1 : 0,
title: action.title,
message: action.message
}));
case ALERT_DISMISS:
return state.filterNot(item => item.get('key') === action.alert.key);
case ALERT_CLEAR:
return state.clear();
default:
return state;
}
};

View file

@ -0,0 +1,14 @@
import { STATUS_CARD_FETCH_SUCCESS } from '../actions/cards';
import Immutable from 'immutable';
const initialState = Immutable.Map();
export default function cards(state = initialState, action) {
switch(action.type) {
case STATUS_CARD_FETCH_SUCCESS:
return state.set(action.id, Immutable.fromJS(action.card));
default:
return state;
}
};

View file

@ -0,0 +1,232 @@
import {
COMPOSE_MOUNT,
COMPOSE_UNMOUNT,
COMPOSE_CHANGE,
COMPOSE_REPLY,
COMPOSE_REPLY_CANCEL,
COMPOSE_MENTION,
COMPOSE_SUBMIT_REQUEST,
COMPOSE_SUBMIT_SUCCESS,
COMPOSE_SUBMIT_FAIL,
COMPOSE_UPLOAD_REQUEST,
COMPOSE_UPLOAD_SUCCESS,
COMPOSE_UPLOAD_FAIL,
COMPOSE_UPLOAD_UNDO,
COMPOSE_UPLOAD_PROGRESS,
COMPOSE_SUGGESTIONS_CLEAR,
COMPOSE_SUGGESTIONS_READY,
COMPOSE_SUGGESTION_SELECT,
COMPOSE_SENSITIVITY_CHANGE,
COMPOSE_SPOILERNESS_CHANGE,
COMPOSE_SPOILER_TEXT_CHANGE,
COMPOSE_VISIBILITY_CHANGE,
COMPOSE_LISTABILITY_CHANGE,
COMPOSE_EMOJI_INSERT
} from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
import uuid from '../uuid';
const initialState = Immutable.Map({
mounted: false,
sensitive: false,
spoiler: false,
spoiler_text: '',
privacy: null,
text: '',
focusDate: null,
preselectDate: null,
in_reply_to: null,
is_submitting: false,
is_uploading: false,
progress: 0,
media_attachments: Immutable.List(),
suggestion_token: null,
suggestions: Immutable.List(),
me: null,
default_privacy: 'public',
resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null
});
function statusToTextMentions(state, status) {
let set = Immutable.OrderedSet([]);
let me = state.get('me');
if (status.getIn(['account', 'id']) !== me) {
set = set.add(`@${status.getIn(['account', 'acct'])} `);
}
return set.union(status.get('mentions').filterNot(mention => mention.get('id') === me).map(mention => `@${mention.get('acct')} `)).join('');
};
function clearAll(state) {
return state.withMutations(map => {
map.set('text', '');
map.set('spoiler', false);
map.set('spoiler_text', '');
map.set('is_submitting', false);
map.set('in_reply_to', null);
map.set('privacy', state.get('default_privacy'));
map.set('sensitive', false);
map.update('media_attachments', list => list.clear());
map.set('idempotencyKey', uuid());
});
};
function appendMedia(state, media) {
return state.withMutations(map => {
map.update('media_attachments', list => list.push(media));
map.set('is_uploading', false);
map.set('resetFileKey', Math.floor((Math.random() * 0x10000)));
map.update('text', oldText => `${oldText.trim()} ${media.get('text_url')}`);
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
};
function removeMedia(state, mediaId) {
const media = state.get('media_attachments').find(item => item.get('id') === mediaId);
const prevSize = state.get('media_attachments').size;
return state.withMutations(map => {
map.update('media_attachments', list => list.filterNot(item => item.get('id') === mediaId));
map.update('text', text => text.replace(media.get('text_url'), '').trim());
map.set('idempotencyKey', uuid());
if (prevSize === 1) {
map.set('sensitive', false);
}
});
};
const insertSuggestion = (state, position, token, completion) => {
return state.withMutations(map => {
map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`);
map.set('suggestion_token', null);
map.update('suggestions', Immutable.List(), list => list.clear());
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
};
const insertEmoji = (state, position, emojiData) => {
const emoji = emojiData.shortname;
return state.withMutations(map => {
map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`);
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
};
const privacyPreference = (a, b) => {
if (a === 'direct' || b === 'direct') {
return 'direct';
} else if (a === 'private' || b === 'private') {
return 'private';
} else if (a === 'unlisted' || b === 'unlisted') {
return 'unlisted';
} else {
return 'public';
}
};
export default function compose(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return clearAll(state.merge(action.state.get('compose')));
case COMPOSE_MOUNT:
return state.set('mounted', true);
case COMPOSE_UNMOUNT:
return state.set('mounted', false);
case COMPOSE_SENSITIVITY_CHANGE:
return state
.set('sensitive', !state.get('sensitive'))
.set('idempotencyKey', uuid());
case COMPOSE_SPOILERNESS_CHANGE:
return state.withMutations(map => {
map.set('spoiler_text', '');
map.set('spoiler', !state.get('spoiler'));
map.set('idempotencyKey', uuid());
});
case COMPOSE_SPOILER_TEXT_CHANGE:
return state
.set('spoiler_text', action.text)
.set('idempotencyKey', uuid());
case COMPOSE_VISIBILITY_CHANGE:
return state
.set('privacy', action.value)
.set('idempotencyKey', uuid());
case COMPOSE_CHANGE:
return state
.set('text', action.text)
.set('idempotencyKey', uuid());
case COMPOSE_REPLY:
return state.withMutations(map => {
map.set('in_reply_to', action.status.get('id'));
map.set('text', statusToTextMentions(state, action.status));
map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy')));
map.set('focusDate', new Date());
map.set('preselectDate', new Date());
map.set('idempotencyKey', uuid());
if (action.status.get('spoiler_text').length > 0) {
map.set('spoiler', true);
map.set('spoiler_text', action.status.get('spoiler_text'));
} else {
map.set('spoiler', false);
map.set('spoiler_text', '');
}
});
case COMPOSE_REPLY_CANCEL:
return state.withMutations(map => {
map.set('in_reply_to', null);
map.set('text', '');
map.set('spoiler', false);
map.set('spoiler_text', '');
map.set('privacy', state.get('default_privacy'));
map.set('idempotencyKey', uuid());
});
case COMPOSE_SUBMIT_REQUEST:
return state.set('is_submitting', true);
case COMPOSE_SUBMIT_SUCCESS:
return clearAll(state);
case COMPOSE_SUBMIT_FAIL:
return state.set('is_submitting', false);
case COMPOSE_UPLOAD_REQUEST:
return state.withMutations(map => {
map.set('is_uploading', true);
});
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, Immutable.fromJS(action.media));
case COMPOSE_UPLOAD_FAIL:
return state.set('is_uploading', false);
case COMPOSE_UPLOAD_UNDO:
return removeMedia(state, action.media_id);
case COMPOSE_UPLOAD_PROGRESS:
return state.set('progress', Math.round((action.loaded / action.total) * 100));
case COMPOSE_MENTION:
return state
.update('text', text => `${text}@${action.account.get('acct')} `)
.set('focusDate', new Date())
.set('idempotencyKey', uuid());
case COMPOSE_SUGGESTIONS_CLEAR:
return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null);
case COMPOSE_SUGGESTIONS_READY:
return state.set('suggestions', Immutable.List(action.accounts.map(item => item.id))).set('suggestion_token', action.token);
case COMPOSE_SUGGESTION_SELECT:
return insertSuggestion(state, action.position, action.token, action.completion);
case TIMELINE_DELETE:
if (action.id === state.get('in_reply_to')) {
return state.set('in_reply_to', null);
} else {
return state;
}
case COMPOSE_EMOJI_INSERT:
return insertEmoji(state, action.position, action.emoji);
default:
return state;
}
};

View file

@ -0,0 +1,38 @@
import { combineReducers } from 'redux-immutable';
import timelines from './timelines';
import meta from './meta';
import compose from './compose';
import alerts from './alerts';
import { loadingBarReducer } from 'react-redux-loading-bar';
import modal from './modal';
import user_lists from './user_lists';
import accounts from './accounts';
import accounts_counters from './accounts_counters';
import statuses from './statuses';
import relationships from './relationships';
import search from './search';
import notifications from './notifications';
import settings from './settings';
import status_lists from './status_lists';
import cards from './cards';
import reports from './reports';
export default combineReducers({
timelines,
meta,
compose,
alerts,
loadingBar: loadingBarReducer,
modal,
user_lists,
status_lists,
accounts,
accounts_counters,
statuses,
relationships,
search,
notifications,
settings,
cards,
reports
});

View file

@ -0,0 +1,17 @@
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const initialState = Immutable.Map({
streaming_api_base_url: null,
access_token: null,
me: null
});
export default function meta(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('meta'));
default:
return state;
}
};

View file

@ -0,0 +1,18 @@
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
import Immutable from 'immutable';
const initialState = {
modalType: null,
modalProps: {}
};
export default function modal(state = initialState, action) {
switch(action.type) {
case MODAL_OPEN:
return { modalType: action.modalType, modalProps: action.modalProps };
case MODAL_CLOSE:
return initialState;
default:
return state;
}
};

View file

@ -0,0 +1,104 @@
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
NOTIFICATIONS_REFRESH_REQUEST,
NOTIFICATIONS_EXPAND_REQUEST,
NOTIFICATIONS_REFRESH_FAIL,
NOTIFICATIONS_EXPAND_FAIL,
NOTIFICATIONS_CLEAR,
NOTIFICATIONS_SCROLL_TOP
} from '../actions/notifications';
import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts';
import Immutable from 'immutable';
const initialState = Immutable.Map({
items: Immutable.List(),
next: null,
top: true,
unread: 0,
loaded: false,
isLoading: true
});
const notificationToMap = notification => Immutable.Map({
id: notification.id,
type: notification.type,
account: notification.account.id,
status: notification.status ? notification.status.id : null
});
const normalizeNotification = (state, notification) => {
if (!state.get('top')) {
state = state.update('unread', unread => unread + 1);
}
return state.update('items', list => list.unshift(notificationToMap(notification)));
};
const normalizeNotifications = (state, notifications, next) => {
let items = Immutable.List();
const loaded = state.get('loaded');
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(n));
});
if (state.get('next') === null) {
state = state.set('next', next);
}
return state
.update('items', list => loaded ? items.concat(list) : list.concat(items))
.set('loaded', true)
.set('isLoading', false);
};
const appendNormalizedNotifications = (state, notifications, next) => {
let items = Immutable.List();
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(n));
});
return state
.update('items', list => list.concat(items))
.set('next', next)
.set('isLoading', false);
};
const filterNotifications = (state, relationship) => {
return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id));
};
const updateTop = (state, top) => {
if (top) {
state = state.set('unread', 0);
}
return state.set('top', top);
};
export default function notifications(state = initialState, action) {
switch(action.type) {
case NOTIFICATIONS_REFRESH_REQUEST:
case NOTIFICATIONS_EXPAND_REQUEST:
case NOTIFICATIONS_REFRESH_FAIL:
case NOTIFICATIONS_EXPAND_FAIL:
return state.set('isLoading', true);
case NOTIFICATIONS_SCROLL_TOP:
return updateTop(state, action.top);
case NOTIFICATIONS_UPDATE:
return normalizeNotification(state, action.notification);
case NOTIFICATIONS_REFRESH_SUCCESS:
return normalizeNotifications(state, action.notifications, action.next);
case NOTIFICATIONS_EXPAND_SUCCESS:
return appendNormalizedNotifications(state, action.notifications, action.next);
case ACCOUNT_BLOCK_SUCCESS:
return filterNotifications(state, action.relationship);
case NOTIFICATIONS_CLEAR:
return state.set('items', Immutable.List()).set('next', null);
default:
return state;
}
};

View file

@ -0,0 +1,38 @@
import {
ACCOUNT_FOLLOW_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_UNBLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNMUTE_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS
} from '../actions/accounts';
import Immutable from 'immutable';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, Immutable.fromJS(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:
case ACCOUNT_MUTE_SUCCESS:
case ACCOUNT_UNMUTE_SUCCESS:
return normalizeRelationship(state, action.relationship);
case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships);
default:
return state;
}
};

View file

@ -0,0 +1,60 @@
import {
REPORT_INIT,
REPORT_SUBMIT_REQUEST,
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE
} from '../actions/reports';
import Immutable from 'immutable';
const initialState = Immutable.Map({
new: Immutable.Map({
isSubmitting: false,
account_id: null,
status_ids: Immutable.Set(),
comment: ''
})
});
export default function reports(state = initialState, action) {
switch(action.type) {
case REPORT_INIT:
return state.withMutations(map => {
map.setIn(['new', 'isSubmitting'], false);
map.setIn(['new', 'account_id'], action.account.get('id'));
if (state.getIn(['new', 'account_id']) !== action.account.get('id')) {
map.setIn(['new', 'status_ids'], action.status ? Immutable.Set([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : Immutable.Set());
map.setIn(['new', 'comment'], '');
} else {
map.updateIn(['new', 'status_ids'], Immutable.Set(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
}
});
case REPORT_STATUS_TOGGLE:
return state.updateIn(['new', 'status_ids'], Immutable.Set(), set => {
if (action.checked) {
return set.add(action.statusId);
}
return set.remove(action.statusId);
});
case REPORT_COMMENT_CHANGE:
return state.setIn(['new', 'comment'], action.comment);
case REPORT_SUBMIT_REQUEST:
return state.setIn(['new', 'isSubmitting'], true);
case REPORT_SUBMIT_FAIL:
return state.setIn(['new', 'isSubmitting'], false);
case REPORT_CANCEL:
case REPORT_SUBMIT_SUCCESS:
return state.withMutations(map => {
map.setIn(['new', 'account_id'], null);
map.setIn(['new', 'status_ids'], Immutable.Set());
map.setIn(['new', 'comment'], '');
map.setIn(['new', 'isSubmitting'], false);
});
default:
return state;
}
};

View file

@ -0,0 +1,96 @@
import {
SEARCH_CHANGE,
SEARCH_CLEAR,
SEARCH_FETCH_SUCCESS,
SEARCH_SHOW
} from '../actions/search';
import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
import Immutable from 'immutable';
const initialState = Immutable.Map({
value: '',
submitted: false,
hidden: false,
results: Immutable.Map()
});
const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => {
let newSuggestions = [];
if (accounts.length > 0) {
newSuggestions.push({
title: 'account',
items: accounts.map(item => ({
type: 'account',
id: item.id,
value: item.acct
}))
});
}
if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 || hashtags.length > 0) {
let hashtagItems = hashtags.map(item => ({
type: 'hashtag',
id: item,
value: `#${item}`
}));
if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) {
hashtagItems.unshift({
type: 'hashtag',
id: value,
value: `#${value}`
});
}
if (hashtagItems.length > 0) {
newSuggestions.push({
title: 'hashtag',
items: hashtagItems
});
}
}
if (statuses.length > 0) {
newSuggestions.push({
title: 'status',
items: statuses.map(item => ({
type: 'status',
id: item.id,
value: item.id
}))
});
}
return state.withMutations(map => {
map.set('suggestions', newSuggestions);
map.set('loaded_value', value);
});
};
export default function search(state = initialState, action) {
switch(action.type) {
case SEARCH_CHANGE:
return state.set('value', action.value);
case SEARCH_CLEAR:
return state.withMutations(map => {
map.set('value', '');
map.set('results', Immutable.Map());
map.set('submitted', false);
map.set('hidden', false);
});
case SEARCH_SHOW:
return state.set('hidden', false);
case COMPOSE_REPLY:
case COMPOSE_MENTION:
return state.set('hidden', true);
case SEARCH_FETCH_SUCCESS:
return state.set('results', Immutable.Map({
accounts: Immutable.List(action.results.accounts.map(item => item.id)),
statuses: Immutable.List(action.results.statuses.map(item => item.id)),
hashtags: Immutable.List(action.results.hashtags)
})).set('submitted', true);
default:
return state;
}
};

View file

@ -0,0 +1,52 @@
import { SETTING_CHANGE } from '../actions/settings';
import { STORE_HYDRATE } from '../actions/store';
import Immutable from 'immutable';
const initialState = Immutable.Map({
onboarded: false,
home: Immutable.Map({
shows: Immutable.Map({
reblog: true,
reply: true
}),
regex: Immutable.Map({
body: ''
})
}),
notifications: Immutable.Map({
alerts: Immutable.Map({
follow: true,
favourite: true,
reblog: true,
mention: true
}),
shows: Immutable.Map({
follow: true,
favourite: true,
reblog: true,
mention: true
}),
sounds: Immutable.Map({
follow: true,
favourite: true,
reblog: true,
mention: true
})
})
});
export default function settings(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.mergeDeep(action.state.get('settings'));
case SETTING_CHANGE:
return state.setIn(action.key, action.value);
default:
return state;
}
};

View file

@ -0,0 +1,39 @@
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS
} from '../actions/favourites';
import Immutable from 'immutable';
const initialState = Immutable.Map({
favourites: Immutable.Map({
next: null,
loaded: false,
items: Immutable.List()
})
});
const normalizeList = (state, listType, statuses, next) => {
return state.update(listType, listMap => listMap.withMutations(map => {
map.set('next', next);
map.set('loaded', true);
map.set('items', Immutable.List(statuses.map(item => item.id)));
}));
};
const appendToList = (state, listType, statuses, next) => {
return state.update(listType, listMap => listMap.withMutations(map => {
map.set('next', next);
map.set('items', map.get('items').concat(statuses.map(item => item.id)));
}));
};
export default function statusLists(state = initialState, action) {
switch(action.type) {
case FAVOURITED_STATUSES_FETCH_SUCCESS:
return normalizeList(state, 'favourites', action.statuses, action.next);
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
return appendToList(state, 'favourites', action.statuses, action.next);
default:
return state;
}
};

View file

@ -0,0 +1,124 @@
import {
REBLOG_REQUEST,
REBLOG_SUCCESS,
REBLOG_FAIL,
UNREBLOG_SUCCESS,
FAVOURITE_REQUEST,
FAVOURITE_SUCCESS,
FAVOURITE_FAIL,
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,
ACCOUNT_BLOCK_SUCCESS
} from '../actions/accounts';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS
} from '../actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS
} from '../actions/favourites';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import Immutable from 'immutable';
const normalizeStatus = (state, status) => {
if (!status) {
return state;
}
const normalStatus = { ...status };
normalStatus.account = status.account.id;
if (status.reblog && status.reblog.id) {
state = normalizeStatus(state, status.reblog);
normalStatus.reblog = status.reblog.id;
}
const linebreakComplemented = status.content.replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
normalStatus.unescaped_content = new DOMParser().parseFromString(linebreakComplemented, 'text/html').documentElement.textContent;
return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus)));
};
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 filterStatuses = (state, relationship) => {
state.forEach(status => {
if (status.get('account') !== relationship.id) {
return;
}
state = deleteStatus(state, status.get('id'), state.filter(item => item.get('reblog') === status.get('id')));
});
return state;
};
const initialState = Immutable.Map();
export default function statuses(state = initialState, action) {
switch(action.type) {
case TIMELINE_UPDATE:
case STATUS_FETCH_SUCCESS:
case NOTIFICATIONS_UPDATE:
return normalizeStatus(state, action.status);
case REBLOG_SUCCESS:
case UNREBLOG_SUCCESS:
case FAVOURITE_SUCCESS:
case UNFAVOURITE_SUCCESS:
return normalizeStatus(state, action.response);
case FAVOURITE_REQUEST:
return state.setIn([action.status.get('id'), 'favourited'], true);
case FAVOURITE_FAIL:
return state.setIn([action.status.get('id'), 'favourited'], false);
case REBLOG_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], true);
case REBLOG_FAIL:
return state.setIn([action.status.get('id'), 'reblogged'], false);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
case CONTEXT_FETCH_SUCCESS:
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
case FAVOURITED_STATUSES_FETCH_SUCCESS:
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
case SEARCH_FETCH_SUCCESS:
return normalizeStatuses(state, action.statuses);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case ACCOUNT_BLOCK_SUCCESS:
return filterStatuses(state, action.relationship);
default:
return state;
}
};

View file

@ -0,0 +1,317 @@
import {
TIMELINE_REFRESH_REQUEST,
TIMELINE_REFRESH_SUCCESS,
TIMELINE_REFRESH_FAIL,
TIMELINE_UPDATE,
TIMELINE_DELETE,
TIMELINE_EXPAND_SUCCESS,
TIMELINE_EXPAND_REQUEST,
TIMELINE_EXPAND_FAIL,
TIMELINE_SCROLL_TOP,
TIMELINE_CONNECT,
TIMELINE_DISCONNECT
} from '../actions/timelines';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS
} from '../actions/interactions';
import {
ACCOUNT_TIMELINE_FETCH_REQUEST,
ACCOUNT_TIMELINE_FETCH_SUCCESS,
ACCOUNT_TIMELINE_FETCH_FAIL,
ACCOUNT_TIMELINE_EXPAND_REQUEST,
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
ACCOUNT_TIMELINE_EXPAND_FAIL,
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS
} from '../actions/accounts';
import {
CONTEXT_FETCH_SUCCESS
} from '../actions/statuses';
import Immutable from 'immutable';
const initialState = Immutable.Map({
home: Immutable.Map({
path: () => '/api/v1/timelines/home',
next: null,
isLoading: false,
online: false,
loaded: false,
top: true,
unread: 0,
items: Immutable.List()
}),
public: Immutable.Map({
path: () => '/api/v1/timelines/public',
next: null,
isLoading: false,
online: false,
loaded: false,
top: true,
unread: 0,
items: Immutable.List()
}),
community: Immutable.Map({
path: () => '/api/v1/timelines/public',
next: null,
params: { local: true },
isLoading: false,
online: false,
loaded: false,
top: true,
unread: 0,
items: Immutable.List()
}),
tag: Immutable.Map({
path: (id) => `/api/v1/timelines/tag/${id}`,
next: null,
isLoading: false,
id: null,
loaded: false,
top: true,
unread: 0,
items: Immutable.List()
}),
accounts_timelines: Immutable.Map(),
ancestors: Immutable.Map(),
descendants: Immutable.Map()
});
const normalizeStatus = (state, status) => {
const replyToId = status.get('in_reply_to_id');
const id = 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));
}
if (!state.getIn(['ancestors', id], Immutable.List()).includes(replyToId)) {
state = state.updateIn(['ancestors', id], Immutable.List(), set => set.push(replyToId));
}
}
return state;
};
const normalizeTimeline = (state, timeline, statuses, next) => {
let ids = Immutable.List();
const loaded = state.getIn([timeline, 'loaded']);
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
ids = ids.set(i, status.get('id'));
});
state = state.setIn([timeline, 'loaded'], true);
state = state.setIn([timeline, 'isLoading'], false);
if (state.getIn([timeline, 'next']) === null) {
state = state.setIn([timeline, 'next'], next);
}
return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? ids.concat(list) : ids));
};
const appendNormalizedTimeline = (state, timeline, statuses, next) => {
let moreIds = Immutable.List();
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
moreIds = moreIds.set(i, status.get('id'));
});
state = state.setIn([timeline, 'isLoading'], false);
state = state.setIn([timeline, 'next'], next);
return state.updateIn([timeline, 'items'], Immutable.List(), list => list.concat(moreIds));
};
const normalizeAccountTimeline = (state, accountId, statuses, replace = false) => {
let ids = Immutable.List();
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
ids = ids.set(i, status.get('id'));
});
return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
.set('isLoading', false)
.set('loaded', true)
.set('next', true)
.update('items', Immutable.List(), list => (replace ? ids : ids.concat(list))));
};
const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
let moreIds = Immutable.List([]);
statuses.forEach((status, i) => {
state = normalizeStatus(state, status);
moreIds = moreIds.set(i, status.get('id'));
});
return state.updateIn(['accounts_timelines', accountId], Immutable.Map(), map => map
.set('isLoading', false)
.set('next', next)
.update('items', list => list.concat(moreIds)));
};
const updateTimeline = (state, timeline, status, references) => {
const top = state.getIn([timeline, 'top']);
state = normalizeStatus(state, status);
if (!top) {
state = state.updateIn([timeline, 'unread'], unread => unread + 1);
}
state = state.updateIn([timeline, 'items'], Immutable.List(), list => {
if (top && list.size > 40) {
list = list.take(20);
}
if (list.includes(status.get('id'))) {
return list;
}
const reblogOfId = status.getIn(['reblog', 'id'], null);
if (reblogOfId !== null) {
list = list.filterNot(itemId => references.includes(itemId));
}
return list.unshift(status.get('id'));
});
return state;
};
const deleteStatus = (state, id, accountId, references, reblogOf) => {
if (reblogOf) {
// If we are deleting a reblog, just replace reblog with its original
return state.updateIn(['home', 'items'], list => list.map(item => item === id ? reblogOf : item));
}
// Remove references from timelines
['home', 'public', 'community', 'tag'].forEach(function (timeline) {
state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id));
});
// Remove references from account timelines
state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
// 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));
});
state.getIn(['ancestors', id], Immutable.List()).forEach(ancestorId => {
state = state.updateIn(['descendants', ancestorId], Immutable.List(), list => list.filterNot(itemId => itemId === id));
});
state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
// Remove reblogs of deleted status
references.forEach(ref => {
state = deleteStatus(state, ref[0], ref[1], []);
});
return state;
};
const filterTimelines = (state, relationship, statuses) => {
let references;
statuses.forEach(status => {
if (status.get('account') !== relationship.id) {
return;
}
references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]);
state = deleteStatus(state, status.get('id'), status.get('account'), references);
});
return state;
};
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', id], ancestorsIds);
map.setIn(['descendants', id], descendantsIds);
});
};
const resetTimeline = (state, timeline, id) => {
if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) {
state = state.update(timeline, map => map
.set('id', id)
.set('isLoading', true)
.set('loaded', false)
.set('next', null)
.set('top', true)
.update('items', list => list.clear()));
} else {
state = state.setIn([timeline, 'isLoading'], true);
}
return state;
};
const updateTop = (state, timeline, top) => {
if (top) {
state = state.setIn([timeline, 'unread'], 0);
}
return state.setIn([timeline, 'top'], top);
};
export default function timelines(state = initialState, action) {
switch(action.type) {
case TIMELINE_REFRESH_REQUEST:
case TIMELINE_EXPAND_REQUEST:
return resetTimeline(state, action.timeline, action.id);
case TIMELINE_REFRESH_FAIL:
case TIMELINE_EXPAND_FAIL:
return state.setIn([action.timeline, 'isLoading'], false);
case TIMELINE_REFRESH_SUCCESS:
return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next);
case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
case CONTEXT_FETCH_SUCCESS:
return normalizeContext(state, action.id, Immutable.fromJS(action.ancestors), Immutable.fromJS(action.descendants));
case ACCOUNT_TIMELINE_FETCH_REQUEST:
case ACCOUNT_TIMELINE_EXPAND_REQUEST:
return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
case ACCOUNT_TIMELINE_FETCH_FAIL:
case ACCOUNT_TIMELINE_EXPAND_FAIL:
return state.updateIn(['accounts_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
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), action.next);
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
return filterTimelines(state, action.relationship, action.statuses);
case TIMELINE_SCROLL_TOP:
return updateTop(state, action.timeline, action.top);
case TIMELINE_CONNECT:
return state.setIn([action.timeline, 'online'], true);
case TIMELINE_DISCONNECT:
return state.setIn([action.timeline, 'online'], false);
default:
return state;
}
};

View file

@ -0,0 +1,80 @@
import {
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_EXPAND_SUCCESS,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
FOLLOW_REQUEST_AUTHORIZE_SUCCESS,
FOLLOW_REQUEST_REJECT_SUCCESS
} from '../actions/accounts';
import {
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS
} from '../actions/interactions';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS
} from '../actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS
} from '../actions/mutes';
import Immutable from 'immutable';
const initialState = Immutable.Map({
followers: Immutable.Map(),
following: Immutable.Map(),
reblogged_by: Immutable.Map(),
favourited_by: Immutable.Map(),
follow_requests: Immutable.Map(),
blocks: Immutable.Map(),
mutes: Immutable.Map()
});
const normalizeList = (state, type, id, accounts, next) => {
return state.setIn([type, id], Immutable.Map({
next,
items: Immutable.List(accounts.map(item => item.id))
}));
};
const appendToList = (state, type, id, accounts, next) => {
return state.updateIn([type, id], map => {
return map.set('next', next).update('items', list => list.concat(accounts.map(item => item.id)));
});
};
export default function userLists(state = initialState, action) {
switch(action.type) {
case FOLLOWERS_FETCH_SUCCESS:
return normalizeList(state, 'followers', action.id, action.accounts, action.next);
case FOLLOWERS_EXPAND_SUCCESS:
return appendToList(state, 'followers', action.id, action.accounts, action.next);
case FOLLOWING_FETCH_SUCCESS:
return normalizeList(state, 'following', action.id, action.accounts, action.next);
case FOLLOWING_EXPAND_SUCCESS:
return appendToList(state, 'following', action.id, action.accounts, action.next);
case REBLOGS_FETCH_SUCCESS:
return state.setIn(['reblogged_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
case FAVOURITES_FETCH_SUCCESS:
return state.setIn(['favourited_by', action.id], Immutable.List(action.accounts.map(item => item.id)));
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return state.setIn(['follow_requests', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
return state.updateIn(['follow_requests', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['follow_requests', 'next'], action.next);
case FOLLOW_REQUEST_AUTHORIZE_SUCCESS:
case FOLLOW_REQUEST_REJECT_SUCCESS:
return state.updateIn(['follow_requests', 'items'], list => list.filterNot(item => item === action.id));
case BLOCKS_FETCH_SUCCESS:
return state.setIn(['blocks', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
case BLOCKS_EXPAND_SUCCESS:
return state.updateIn(['blocks', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['blocks', 'next'], action.next);
case MUTES_FETCH_SUCCESS:
return state.setIn(['mutes', 'items'], Immutable.List(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
case MUTES_EXPAND_SUCCESS:
return state.updateIn(['mutes', 'items'], list => list.concat(action.accounts.map(item => item.id))).setIn(['mutes', 'next'], action.next);
default:
return state;
}
};