Forking glitch theme

This commit is contained in:
kibigo!
2017-11-17 19:11:18 -08:00
parent 5a9982b425
commit 45c44989c8
333 changed files with 1714 additions and 4235 deletions

View File

@@ -1,135 +0,0 @@
import {
ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_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 emojify from '../features/emoji/emoji';
import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
const normalizeAccount = (state, account) => {
account = { ...account };
delete account.followers_count;
delete account.following_count;
delete account.statuses_count;
const displayName = account.display_name.length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName));
account.note_emojified = emojify(account.note);
return state.set(account.id, 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 = ImmutableMap();
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 action.accounts ? normalizeAccounts(state, action.accounts) : state;
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 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

@@ -1,136 +0,0 @@
import {
ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_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 { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, 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 = ImmutableMap();
export default function accountsCounters(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('accounts').map(item => fromJS({
followers_count: item.get('followers_count'),
following_count: item.get('following_count'),
statuses_count: item.get('statuses_count'),
})));
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 action.accounts ? normalizeAccounts(state, action.accounts) : state;
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 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:
if (action.alreadyFollowing) { return state; }
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

@@ -1,25 +0,0 @@
import {
ALERT_SHOW,
ALERT_DISMISS,
ALERT_CLEAR,
} from '../actions/alerts';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableList([]);
export default function alerts(state = initialState, action) {
switch(action.type) {
case ALERT_SHOW:
return state.push(ImmutableMap({
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

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

View File

@@ -1,307 +0,0 @@
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_ADVANCED_OPTIONS_CHANGE,
COMPOSE_SENSITIVITY_CHANGE,
COMPOSE_SPOILERNESS_CHANGE,
COMPOSE_SPOILER_TEXT_CHANGE,
COMPOSE_VISIBILITY_CHANGE,
COMPOSE_COMPOSING_CHANGE,
COMPOSE_EMOJI_INSERT,
COMPOSE_UPLOAD_CHANGE_REQUEST,
COMPOSE_UPLOAD_CHANGE_SUCCESS,
COMPOSE_UPLOAD_CHANGE_FAIL,
COMPOSE_DOODLE_SET,
COMPOSE_RESET,
} from '../actions/compose';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STORE_HYDRATE } from '../actions/store';
import { Map as ImmutableMap, List as ImmutableList, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
import uuid from '../uuid';
import { me } from '../initial_state';
const initialState = ImmutableMap({
mounted: false,
advanced_options: ImmutableMap({
do_not_federate: false,
}),
sensitive: false,
spoiler: false,
spoiler_text: '',
privacy: null,
text: '',
focusDate: null,
preselectDate: null,
in_reply_to: null,
is_composing: false,
is_submitting: false,
is_uploading: false,
progress: 0,
media_attachments: ImmutableList(),
suggestion_token: null,
suggestions: ImmutableList(),
default_advanced_options: ImmutableMap({
do_not_federate: false,
}),
default_privacy: 'public',
default_sensitive: false,
resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null,
doodle: ImmutableMap({
fg: 'rgb( 0, 0, 0)',
bg: 'rgb(255, 255, 255)',
swapped: false,
mode: 'draw',
size: 'normal',
weight: 2,
opacity: 1,
adaptiveStroke: true,
smoothing: false,
}),
});
function statusToTextMentions(state, status) {
let set = ImmutableOrderedSet([]);
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('advanced_options', state.get('default_advanced_options'));
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) {
const prevSize = state.get('media_attachments').size;
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());
if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) {
map.set('sensitive', true);
}
});
};
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}\u200B${oldText.slice(position + token.length)}`);
map.set('suggestion_token', null);
map.update('suggestions', ImmutableList(), list => list.clear());
map.set('focusDate', new Date());
map.set('idempotencyKey', uuid());
});
};
const insertEmoji = (state, position, emojiData) => {
const emoji = emojiData.native;
return state.withMutations(map => {
map.update('text', oldText => `${oldText.slice(0, position)}${emoji}\u200B${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';
}
};
const hydrate = (state, hydratedState) => {
state = clearAll(state.merge(hydratedState));
if (hydratedState.has('text')) {
state = state.set('text', hydratedState.get('text'));
}
return state;
};
export default function compose(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return hydrate(state, action.state.get('compose'));
case COMPOSE_MOUNT:
return state.set('mounted', true);
case COMPOSE_UNMOUNT:
return state
.set('mounted', false)
.set('is_composing', false);
case COMPOSE_ADVANCED_OPTIONS_CHANGE:
return state
.set('advanced_options',
state.get('advanced_options').set(action.option, !state.getIn(['advanced_options', action.option])))
.set('idempotencyKey', uuid());
case COMPOSE_SENSITIVITY_CHANGE:
return state.withMutations(map => {
if (!state.get('spoiler')) {
map.set('sensitive', !state.get('sensitive'));
}
map.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());
if (!state.get('sensitive') && state.get('media_attachments').size >= 1) {
map.set('sensitive', true);
}
});
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_COMPOSING_CHANGE:
return state.set('is_composing', action.value);
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('advanced_options', new ImmutableMap({
do_not_federate: /👁\ufe0f?<\/p>$/.test(action.status.get('content')),
}));
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:
case COMPOSE_RESET:
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('advanced_options', state.get('default_advanced_options'));
map.set('idempotencyKey', uuid());
});
case COMPOSE_SUBMIT_REQUEST:
case COMPOSE_UPLOAD_CHANGE_REQUEST:
return state.set('is_submitting', true);
case COMPOSE_SUBMIT_SUCCESS:
return clearAll(state);
case COMPOSE_SUBMIT_FAIL:
case COMPOSE_UPLOAD_CHANGE_FAIL:
return state.set('is_submitting', false);
case COMPOSE_UPLOAD_REQUEST:
return state.set('is_uploading', true);
case COMPOSE_UPLOAD_SUCCESS:
return appendMedia(state, 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', ImmutableList(), list => list.clear()).set('suggestion_token', null);
case COMPOSE_SUGGESTIONS_READY:
return state.set('suggestions', ImmutableList(action.accounts ? action.accounts.map(item => item.id) : action.emojis)).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);
case COMPOSE_UPLOAD_CHANGE_SUCCESS:
return state
.set('is_submitting', false)
.update('media_attachments', list => list.map(item => {
if (item.get('id') === action.media.id) {
return item.set('description', action.media.description);
}
return item;
}));
case COMPOSE_DOODLE_SET:
return state.mergeIn(['doodle'], action.options);
default:
return state;
}
};

View File

@@ -1,61 +0,0 @@
import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses';
import { TIMELINE_DELETE, TIMELINE_CONTEXT_UPDATE } from '../actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
ancestors: ImmutableMap(),
descendants: ImmutableMap(),
});
const normalizeContext = (state, id, ancestors, descendants) => {
const ancestorsIds = ImmutableList(ancestors.map(ancestor => ancestor.id));
const descendantsIds = ImmutableList(descendants.map(descendant => descendant.id));
return state.withMutations(map => {
map.setIn(['ancestors', id], ancestorsIds);
map.setIn(['descendants', id], descendantsIds);
});
};
const deleteFromContexts = (state, id) => {
state.getIn(['descendants', id], ImmutableList()).forEach(descendantId => {
state = state.updateIn(['ancestors', descendantId], ImmutableList(), list => list.filterNot(itemId => itemId === id));
});
state.getIn(['ancestors', id], ImmutableList()).forEach(ancestorId => {
state = state.updateIn(['descendants', ancestorId], ImmutableList(), list => list.filterNot(itemId => itemId === id));
});
state = state.deleteIn(['descendants', id]).deleteIn(['ancestors', id]);
return state;
};
const updateContext = (state, status, references) => {
return state.update('descendants', map => {
references.forEach(parentId => {
map = map.update(parentId, ImmutableList(), list => {
if (list.includes(status.id)) {
return list;
}
return list.push(status.id);
});
});
return map;
});
};
export default function contexts(state = initialState, action) {
switch(action.type) {
case CONTEXT_FETCH_SUCCESS:
return normalizeContext(state, action.id, action.ancestors, action.descendants);
case TIMELINE_DELETE:
return deleteFromContexts(state, action.id);
case TIMELINE_CONTEXT_UPDATE:
return updateContext(state, action.status, action.references);
default:
return state;
}
};

View File

@@ -1,16 +0,0 @@
import { List as ImmutableList } from 'immutable';
import { STORE_HYDRATE } from '../actions/store';
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
import { buildCustomEmojis } from '../features/emoji/emoji';
const initialState = ImmutableList();
export default function custom_emojis(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
emojiSearch('', { custom: buildCustomEmojis(action.state.get('custom_emojis', [])) });
return action.state.get('custom_emojis');
default:
return state;
}
};

View File

@@ -1,23 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import { HEIGHT_CACHE_SET, HEIGHT_CACHE_CLEAR } from '../actions/height_cache';
const initialState = ImmutableMap();
const setHeight = (state, key, id, height) => {
return state.update(key, ImmutableMap(), map => map.set(id, height));
};
const clearHeights = () => {
return ImmutableMap();
};
export default function statuses(state = initialState, action) {
switch(action.type) {
case HEIGHT_CACHE_SET:
return setHeight(state, action.key, action.id, action.height);
case HEIGHT_CACHE_CLEAR:
return clearHeights();
default:
return state;
}
};

View File

@@ -1,54 +0,0 @@
import { combineReducers } from 'redux-immutable';
import timelines from './timelines';
import meta from './meta';
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 settings from './settings';
import local_settings from '../../glitch/reducers/local_settings';
import push_notifications from './push_notifications';
import status_lists from './status_lists';
import cards from './cards';
import mutes from './mutes';
import reports from './reports';
import contexts from './contexts';
import compose from './compose';
import search from './search';
import media_attachments from './media_attachments';
import notifications from './notifications';
import height_cache from './height_cache';
import custom_emojis from './custom_emojis';
const reducers = {
timelines,
meta,
alerts,
loadingBar: loadingBarReducer,
modal,
user_lists,
status_lists,
accounts,
accounts_counters,
statuses,
relationships,
settings,
local_settings,
push_notifications,
cards,
mutes,
reports,
contexts,
compose,
search,
media_attachments,
notifications,
height_cache,
custom_emojis,
};
export default combineReducers(reducers);

View File

@@ -1,15 +0,0 @@
import { STORE_HYDRATE } from '../actions/store';
import { Map as ImmutableMap } from 'immutable';
const initialState = ImmutableMap({
accept_content_types: [],
});
export default function meta(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return state.merge(action.state.get('media_attachments'));
default:
return state;
}
};

View File

@@ -1,16 +0,0 @@
import { STORE_HYDRATE } from '../actions/store';
import { Map as ImmutableMap } from 'immutable';
const initialState = ImmutableMap({
streaming_api_base_url: null,
access_token: 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

@@ -1,17 +0,0 @@
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal';
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

@@ -1,29 +0,0 @@
import Immutable from 'immutable';
import {
MUTES_INIT_MODAL,
MUTES_TOGGLE_HIDE_NOTIFICATIONS,
} from '../actions/mutes';
const initialState = Immutable.Map({
new: Immutable.Map({
isSubmitting: false,
account: null,
notifications: true,
}),
});
export default function mutes(state = initialState, action) {
switch (action.type) {
case MUTES_INIT_MODAL:
return state.withMutations((state) => {
state.setIn(['new', 'isSubmitting'], false);
state.setIn(['new', 'account'], action.account);
state.setIn(['new', 'notifications'], true);
});
case MUTES_TOGGLE_HIDE_NOTIFICATIONS:
return state.updateIn(['new', 'notifications'], (old) => !old);
default:
return state;
}
}

View File

@@ -1,191 +0,0 @@
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,
NOTIFICATIONS_DELETE_MARKED_REQUEST,
NOTIFICATIONS_DELETE_MARKED_SUCCESS,
NOTIFICATION_MARK_FOR_DELETE,
NOTIFICATIONS_DELETE_MARKED_FAIL,
NOTIFICATIONS_ENTER_CLEARING_MODE,
NOTIFICATIONS_MARK_ALL_FOR_DELETE,
} from '../actions/notifications';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
} from '../actions/accounts';
import { TIMELINE_DELETE } from '../actions/timelines';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
items: ImmutableList(),
next: null,
top: true,
unread: 0,
loaded: false,
isLoading: true,
cleaningMode: false,
// notification removal mark of new notifs loaded whilst cleaningMode is true.
markNewForDelete: false,
});
const notificationToMap = (state, notification) => ImmutableMap({
id: notification.id,
type: notification.type,
account: notification.account.id,
markedForDelete: state.get('markNewForDelete'),
status: notification.status ? notification.status.id : null,
});
const normalizeNotification = (state, notification) => {
const top = state.get('top');
if (!top) {
state = state.update('unread', unread => unread + 1);
}
return state.update('items', list => {
if (top && list.size > 40) {
list = list.take(20);
}
return list.unshift(notificationToMap(state, notification));
});
};
const normalizeNotifications = (state, notifications, next) => {
let items = ImmutableList();
const loaded = state.get('loaded');
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(state, 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 = ImmutableList();
notifications.forEach((n, i) => {
items = items.set(i, notificationToMap(state, 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);
};
const deleteByStatus = (state, statusId) => {
return state.update('items', list => list.filterNot(item => item.get('status') === statusId));
};
const markForDelete = (state, notificationId, yes) => {
return state.update('items', list => list.map(item => {
if(item.get('id') === notificationId) {
return item.set('markedForDelete', yes);
} else {
return item;
}
}));
};
const markAllForDelete = (state, yes) => {
return state.update('items', list => list.map(item => {
if(yes !== null) {
return item.set('markedForDelete', yes);
} else {
return item.set('markedForDelete', !item.get('markedForDelete'));
}
}));
};
const unmarkAllForDelete = (state) => {
return state.update('items', list => list.map(item => item.set('markedForDelete', false)));
};
const deleteMarkedNotifs = (state) => {
return state.update('items', list => list.filterNot(item => item.get('markedForDelete')));
};
export default function notifications(state = initialState, action) {
let st;
switch(action.type) {
case NOTIFICATIONS_REFRESH_REQUEST:
case NOTIFICATIONS_EXPAND_REQUEST:
case NOTIFICATIONS_DELETE_MARKED_REQUEST:
return state.set('isLoading', true);
case NOTIFICATIONS_DELETE_MARKED_FAIL:
case NOTIFICATIONS_REFRESH_FAIL:
case NOTIFICATIONS_EXPAND_FAIL:
return state.set('isLoading', false);
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:
case ACCOUNT_MUTE_SUCCESS:
return filterNotifications(state, action.relationship);
case NOTIFICATIONS_CLEAR:
return state.set('items', ImmutableList()).set('next', null);
case TIMELINE_DELETE:
return deleteByStatus(state, action.id);
case NOTIFICATION_MARK_FOR_DELETE:
return markForDelete(state, action.id, action.yes);
case NOTIFICATIONS_DELETE_MARKED_SUCCESS:
return deleteMarkedNotifs(state).set('isLoading', false);
case NOTIFICATIONS_ENTER_CLEARING_MODE:
st = state.set('cleaningMode', action.yes);
if (!action.yes) {
return unmarkAllForDelete(st).set('markNewForDelete', false);
} else {
return st;
}
case NOTIFICATIONS_MARK_ALL_FOR_DELETE:
st = state;
if (action.yes === null) {
// Toggle - this is a bit confusing, as it toggles the all-none mode
//st = st.set('markNewForDelete', !st.get('markNewForDelete'));
} else {
st = st.set('markNewForDelete', action.yes);
}
return markAllForDelete(st, action.yes);
default:
return state;
}
};

View File

@@ -1,51 +0,0 @@
import { STORE_HYDRATE } from '../actions/store';
import { SET_BROWSER_SUPPORT, SET_SUBSCRIPTION, CLEAR_SUBSCRIPTION, ALERTS_CHANGE } from '../actions/push_notifications';
import Immutable from 'immutable';
const initialState = Immutable.Map({
subscription: null,
alerts: new Immutable.Map({
follow: false,
favourite: false,
reblog: false,
mention: false,
}),
isSubscribed: false,
browserSupport: false,
});
export default function push_subscriptions(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE: {
const push_subscription = action.state.get('push_subscription');
if (push_subscription) {
return state
.set('subscription', new Immutable.Map({
id: push_subscription.get('id'),
endpoint: push_subscription.get('endpoint'),
}))
.set('alerts', push_subscription.get('alerts') || initialState.get('alerts'))
.set('isSubscribed', true);
}
return state;
}
case SET_SUBSCRIPTION:
return state
.set('subscription', new Immutable.Map({
id: action.subscription.id,
endpoint: action.subscription.endpoint,
}))
.set('alerts', new Immutable.Map(action.subscription.alerts))
.set('isSubscribed', true);
case SET_BROWSER_SUPPORT:
return state.set('browserSupport', action.value);
case CLEAR_SUBSCRIPTION:
return initialState;
case ALERTS_CHANGE:
return state.setIn(action.key, action.value);
default:
return state;
}
};

View File

@@ -1,46 +0,0 @@
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 {
DOMAIN_BLOCK_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS,
} from '../actions/domain_blocks';
import { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
const normalizeRelationships = (state, relationships) => {
relationships.forEach(relationship => {
state = normalizeRelationship(state, relationship);
});
return state;
};
const initialState = ImmutableMap();
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);
case DOMAIN_BLOCK_SUCCESS:
return state.setIn([action.accountId, 'domain_blocking'], true);
case DOMAIN_UNBLOCK_SUCCESS:
return state.setIn([action.accountId, 'domain_blocking'], false);
default:
return state;
}
};

View File

@@ -1,60 +0,0 @@
import {
REPORT_INIT,
REPORT_SUBMIT_REQUEST,
REPORT_SUBMIT_SUCCESS,
REPORT_SUBMIT_FAIL,
REPORT_CANCEL,
REPORT_STATUS_TOGGLE,
REPORT_COMMENT_CHANGE,
} from '../actions/reports';
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
const initialState = ImmutableMap({
new: ImmutableMap({
isSubmitting: false,
account_id: null,
status_ids: ImmutableSet(),
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 ? ImmutableSet([action.status.getIn(['reblog', 'id'], action.status.get('id'))]) : ImmutableSet());
map.setIn(['new', 'comment'], '');
} else if (action.status) {
map.updateIn(['new', 'status_ids'], ImmutableSet(), set => set.add(action.status.getIn(['reblog', 'id'], action.status.get('id'))));
}
});
case REPORT_STATUS_TOGGLE:
return state.updateIn(['new', 'status_ids'], ImmutableSet(), 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'], ImmutableSet());
map.setIn(['new', 'comment'], '');
map.setIn(['new', 'isSubmitting'], false);
});
default:
return state;
}
};

View File

@@ -1,42 +0,0 @@
import {
SEARCH_CHANGE,
SEARCH_CLEAR,
SEARCH_FETCH_SUCCESS,
SEARCH_SHOW,
} from '../actions/search';
import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
value: '',
submitted: false,
hidden: false,
results: ImmutableMap(),
});
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', ImmutableMap());
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', ImmutableMap({
accounts: ImmutableList(action.results.accounts.map(item => item.id)),
statuses: ImmutableList(action.results.statuses.map(item => item.id)),
hashtags: ImmutableList(action.results.hashtags),
})).set('submitted', true);
default:
return state;
}
};

View File

@@ -1,119 +0,0 @@
import { SETTING_CHANGE, SETTING_SAVE } from '../actions/settings';
import { COLUMN_ADD, COLUMN_REMOVE, COLUMN_MOVE } from '../actions/columns';
import { STORE_HYDRATE } from '../actions/store';
import { EMOJI_USE } from '../actions/emojis';
import { Map as ImmutableMap, fromJS } from 'immutable';
import uuid from '../uuid';
const initialState = ImmutableMap({
saved: true,
onboarded: false,
layout: 'auto',
skinTone: 1,
home: ImmutableMap({
shows: ImmutableMap({
reblog: true,
reply: true,
}),
regex: ImmutableMap({
body: '',
}),
}),
notifications: ImmutableMap({
alerts: ImmutableMap({
follow: true,
favourite: true,
reblog: true,
mention: true,
}),
shows: ImmutableMap({
follow: true,
favourite: true,
reblog: true,
mention: true,
}),
sounds: ImmutableMap({
follow: true,
favourite: true,
reblog: true,
mention: true,
}),
}),
community: ImmutableMap({
regex: ImmutableMap({
body: '',
}),
}),
public: ImmutableMap({
regex: ImmutableMap({
body: '',
}),
}),
direct: ImmutableMap({
regex: ImmutableMap({
body: '',
}),
}),
});
const defaultColumns = fromJS([
{ id: 'COMPOSE', uuid: uuid(), params: {} },
{ id: 'HOME', uuid: uuid(), params: {} },
{ id: 'NOTIFICATIONS', uuid: uuid(), params: {} },
]);
const hydrate = (state, settings) => state.mergeDeep(settings).update('columns', (val = defaultColumns) => val);
const moveColumn = (state, uuid, direction) => {
const columns = state.get('columns');
const index = columns.findIndex(item => item.get('uuid') === uuid);
const newIndex = index + direction;
let newColumns;
newColumns = columns.splice(index, 1);
newColumns = newColumns.splice(newIndex, 0, columns.get(index));
return state
.set('columns', newColumns)
.set('saved', false);
};
const updateFrequentEmojis = (state, emoji) => state.update('frequentlyUsedEmojis', ImmutableMap(), map => map.update(emoji.id, 0, count => count + 1)).set('saved', false);
export default function settings(state = initialState, action) {
switch(action.type) {
case STORE_HYDRATE:
return hydrate(state, action.state.get('settings'));
case SETTING_CHANGE:
return state
.setIn(action.key, action.value)
.set('saved', false);
case COLUMN_ADD:
return state
.update('columns', list => list.push(fromJS({ id: action.id, uuid: uuid(), params: action.params })))
.set('saved', false);
case COLUMN_REMOVE:
return state
.update('columns', list => list.filterNot(item => item.get('uuid') === action.uuid))
.set('saved', false);
case COLUMN_MOVE:
return moveColumn(state, action.uuid, action.direction);
case EMOJI_USE:
return updateFrequentEmojis(state, action.emoji);
case SETTING_SAVE:
return state.set('saved', true);
default:
return state;
}
};

View File

@@ -1,75 +0,0 @@
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from '../actions/favourites';
import {
PINNED_STATUSES_FETCH_SUCCESS,
} from '../actions/pin_statuses';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import {
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
PIN_SUCCESS,
UNPIN_SUCCESS,
} from '../actions/interactions';
const initialState = ImmutableMap({
favourites: ImmutableMap({
next: null,
loaded: false,
items: ImmutableList(),
}),
pins: ImmutableMap({
next: null,
loaded: false,
items: ImmutableList(),
}),
});
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', ImmutableList(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)));
}));
};
const prependOneToList = (state, listType, status) => {
return state.update(listType, listMap => listMap.withMutations(map => {
map.set('items', map.get('items').unshift(status.get('id')));
}));
};
const removeOneFromList = (state, listType, status) => {
return state.update(listType, listMap => listMap.withMutations(map => {
map.set('items', map.get('items').filter(item => item !== status.get('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);
case FAVOURITE_SUCCESS:
return prependOneToList(state, 'favourites', action.status);
case UNFAVOURITE_SUCCESS:
return removeOneFromList(state, 'favourites', action.status);
case PINNED_STATUSES_FETCH_SUCCESS:
return normalizeList(state, 'pins', action.statuses, action.next);
case PIN_SUCCESS:
return prependOneToList(state, 'pins', action.status);
case UNPIN_SUCCESS:
return removeOneFromList(state, 'pins', action.status);
default:
return state;
}
};

View File

@@ -1,148 +0,0 @@
import {
REBLOG_REQUEST,
REBLOG_SUCCESS,
REBLOG_FAIL,
UNREBLOG_SUCCESS,
FAVOURITE_REQUEST,
FAVOURITE_SUCCESS,
FAVOURITE_FAIL,
UNFAVOURITE_SUCCESS,
PIN_SUCCESS,
UNPIN_SUCCESS,
} from '../actions/interactions';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
} from '../actions/statuses';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_UPDATE,
TIMELINE_DELETE,
TIMELINE_EXPAND_SUCCESS,
} from '../actions/timelines';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_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 {
PINNED_STATUSES_FETCH_SUCCESS,
} from '../actions/pin_statuses';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import emojify from '../features/emoji/emoji';
import { Map as ImmutableMap, fromJS } from 'immutable';
import escapeTextContentForBrowser from 'escape-html';
const domParser = new DOMParser();
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 searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const emojiMap = normalStatus.emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap);
return state.update(status.id, ImmutableMap(), map => map.mergeDeep(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 = ImmutableMap();
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:
case PIN_SUCCESS:
case UNPIN_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 STATUS_MUTE_SUCCESS:
return state.setIn([action.id, 'muted'], true);
case STATUS_UNMUTE_SUCCESS:
return state.setIn([action.id, 'muted'], false);
case TIMELINE_REFRESH_SUCCESS:
case 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 PINNED_STATUSES_FETCH_SUCCESS:
case SEARCH_FETCH_SUCCESS:
return normalizeStatuses(state, action.statuses);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
return filterStatuses(state, action.relationship);
default:
return state;
}
};

View File

@@ -1,149 +0,0 @@
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 {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
} from '../actions/accounts';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
const initialState = ImmutableMap();
const initialTimeline = ImmutableMap({
unread: 0,
online: false,
top: true,
loaded: false,
isLoading: false,
next: false,
items: ImmutableList(),
});
const normalizeTimeline = (state, timeline, statuses, next) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
const wasLoaded = state.getIn([timeline, 'loaded']);
const hadNext = state.getIn([timeline, 'next']);
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
mMap.set('loaded', true);
mMap.set('isLoading', false);
if (!hadNext) mMap.set('next', next);
mMap.set('items', wasLoaded ? ids.concat(oldIds) : ids);
}));
};
const appendNormalizedTimeline = (state, timeline, statuses, next) => {
const oldIds = state.getIn([timeline, 'items'], ImmutableList());
const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId));
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
mMap.set('isLoading', false);
mMap.set('next', next);
mMap.set('items', oldIds.concat(ids));
}));
};
const updateTimeline = (state, timeline, status, references) => {
const top = state.getIn([timeline, 'top']);
const ids = state.getIn([timeline, 'items'], ImmutableList());
const includesId = ids.includes(status.get('id'));
const unread = state.getIn([timeline, 'unread'], 0);
if (includesId) {
return state;
}
let newIds = ids;
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
if (!top) mMap.set('unread', unread + 1);
if (top && ids.size > 40) newIds = newIds.take(20);
if (status.getIn(['reblog', 'id'], null) !== null) newIds = newIds.filterNot(item => references.includes(item));
mMap.set('items', newIds.unshift(status.get('id')));
}));
};
const deleteStatus = (state, id, accountId, references) => {
state.keySeq().forEach(timeline => {
state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === 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 filterTimeline = (timeline, state, relationship, statuses) =>
state.updateIn([timeline, 'items'], ImmutableList(), list =>
list.filterNot(statusId =>
statuses.getIn([statusId, 'account']) === relationship.id
));
const updateTop = (state, timeline, top) => {
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
if (top) mMap.set('unread', 0);
mMap.set('top', top);
}));
};
export default function timelines(state = initialState, action) {
switch(action.type) {
case TIMELINE_REFRESH_REQUEST:
case TIMELINE_EXPAND_REQUEST:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true));
case TIMELINE_REFRESH_FAIL:
case TIMELINE_EXPAND_FAIL:
return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false));
case TIMELINE_REFRESH_SUCCESS:
return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_EXPAND_SUCCESS:
return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next);
case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, fromJS(action.status), action.references);
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf);
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
return filterTimelines(state, action.relationship, action.statuses);
case ACCOUNT_UNFOLLOW_SUCCESS:
return filterTimeline('home', state, action.relationship, action.statuses);
case TIMELINE_SCROLL_TOP:
return updateTop(state, action.timeline, action.top);
case TIMELINE_CONNECT:
return state.update(action.timeline, initialTimeline, map => map.set('online', true));
case TIMELINE_DISCONNECT:
return state.update(action.timeline, initialTimeline, map => map.set('online', false));
default:
return state;
}
};

View File

@@ -1,80 +0,0 @@
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 { Map as ImmutableMap, List as ImmutableList } from 'immutable';
const initialState = ImmutableMap({
followers: ImmutableMap(),
following: ImmutableMap(),
reblogged_by: ImmutableMap(),
favourited_by: ImmutableMap(),
follow_requests: ImmutableMap(),
blocks: ImmutableMap(),
mutes: ImmutableMap(),
});
const normalizeList = (state, type, id, accounts, next) => {
return state.setIn([type, id], ImmutableMap({
next,
items: ImmutableList(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], ImmutableList(action.accounts.map(item => item.id)));
case FAVOURITES_FETCH_SUCCESS:
return state.setIn(['favourited_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return state.setIn(['follow_requests', 'items'], ImmutableList(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'], ImmutableList(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'], ImmutableList(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;
}
};