diff --git a/app/soapbox/features/list_timeline/index.js b/app/soapbox/features/list_timeline/index.js
deleted file mode 100644
index 7770aa3f2..000000000
--- a/app/soapbox/features/list_timeline/index.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-import { withRouter } from 'react-router-dom';
-
-import { Button, Spinner } from 'soapbox/components/ui';
-import Column from 'soapbox/features/ui/components/column';
-
-import { fetchList, deleteList } from '../../actions/lists';
-import { openModal } from '../../actions/modals';
-import { connectListStream } from '../../actions/streaming';
-import { expandListTimeline } from '../../actions/timelines';
-import MissingIndicator from '../../components/missing_indicator';
-import StatusListContainer from '../ui/containers/status_list_container';
-
-const messages = defineMessages({
- deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' },
- deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
- deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
-});
-
-const mapStateToProps = (state, props) => ({
- list: state.getIn(['lists', props.params.id]),
- // hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-@withRouter
-class ListTimeline extends React.PureComponent {
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- // hasUnread: PropTypes.bool,
- list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
- intl: PropTypes.object.isRequired,
- history: PropTypes.object,
- };
-
- componentDidMount() {
- this.handleConnect(this.props.params.id);
- }
-
- componentWillUnmount() {
- this.handleDisconnect();
- }
-
- componentDidUpdate(prevProps) {
- if (this.props.params.id !== prevProps.params.id) {
- this.handleDisconnect();
- this.handleConnect(this.props.params.id);
- }
- }
-
- handleConnect(id) {
- const { dispatch } = this.props;
-
- dispatch(fetchList(id));
- dispatch(expandListTimeline(id));
-
- this.disconnect = dispatch(connectListStream(id));
- }
-
- handleDisconnect() {
- if (this.disconnect) {
- this.disconnect();
- this.disconnect = null;
- }
- }
-
- handleLoadMore = maxId => {
- const { id } = this.props.params;
- this.props.dispatch(expandListTimeline(id, { maxId }));
- }
-
- handleEditClick = () => {
- this.props.dispatch(openModal('LIST_EDITOR', { listId: this.props.params.id }));
- }
-
- handleDeleteClick = () => {
- const { dispatch, intl } = this.props;
- const { id } = this.props.params;
-
- dispatch(openModal('CONFIRM', {
- icon: require('@tabler/icons/icons/trash.svg'),
- heading: intl.formatMessage(messages.deleteHeading),
- message: intl.formatMessage(messages.deleteMessage),
- confirm: intl.formatMessage(messages.deleteConfirm),
- onConfirm: () => {
- dispatch(deleteList(id));
- this.props.history.push('/lists');
- },
- }));
- }
-
- render() {
- const { list } = this.props;
- const { id } = this.props.params;
- const title = list ? list.get('title') : id;
-
- if (typeof list === 'undefined') {
- return (
-
-
-
-
-
- );
- } else if (list === false) {
- return (
-
- );
- }
-
- const emptyMessage = (
-
-
-
-
-
- );
-
- return (
-
- {/*
-
-
-
-
-
-
-
-
-
-
-
-
- */}
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/list_timeline/index.tsx b/app/soapbox/features/list_timeline/index.tsx
new file mode 100644
index 000000000..2697c201c
--- /dev/null
+++ b/app/soapbox/features/list_timeline/index.tsx
@@ -0,0 +1,125 @@
+import React from 'react';
+import { useEffect } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { useDispatch } from 'react-redux';
+import { useParams } from 'react-router-dom';
+
+import { fetchList } from 'soapbox/actions/lists';
+import { openModal } from 'soapbox/actions/modals';
+import { connectListStream } from 'soapbox/actions/streaming';
+import { expandListTimeline } from 'soapbox/actions/timelines';
+import MissingIndicator from 'soapbox/components/missing_indicator';
+import { Button, Spinner } from 'soapbox/components/ui';
+import Column from 'soapbox/features/ui/components/column';
+import { useAppSelector } from 'soapbox/hooks';
+
+import StatusListContainer from '../ui/containers/status_list_container';
+
+// const messages = defineMessages({
+// deleteHeading: { id: 'confirmations.delete_list.heading', defaultMessage: 'Delete list' },
+// deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
+// deleteConfirm: { id: 'confirmations.delete_list.confirm', defaultMessage: 'Delete' },
+// });
+
+const ListTimeline: React.FC = () => {
+ const dispatch = useDispatch();
+ const { id } = useParams<{ id: string }>();
+ // const intl = useIntl();
+ // const history = useHistory();
+
+ const list = useAppSelector((state) => state.lists.get(id));
+ // const hasUnread = useAppSelector((state) => state.timelines.getIn([`list:${props.params.id}`, 'unread']) > 0);
+
+ useEffect(() => {
+ const disconnect = handleConnect(id);
+
+ return () => {
+ disconnect();
+ };
+ }, [id]);
+
+ const handleConnect = (id: string) => {
+ dispatch(fetchList(id));
+ dispatch(expandListTimeline(id));
+
+ return dispatch(connectListStream(id));
+ };
+
+ const handleLoadMore = (maxId: string) => {
+ dispatch(expandListTimeline(id, { maxId }));
+ };
+
+ const handleEditClick = () => {
+ dispatch(openModal('LIST_EDITOR', { listId: id }));
+ };
+
+ // const handleDeleteClick = () => {
+ // dispatch(openModal('CONFIRM', {
+ // icon: require('@tabler/icons/icons/trash.svg'),
+ // heading: intl.formatMessage(messages.deleteHeading),
+ // message: intl.formatMessage(messages.deleteMessage),
+ // confirm: intl.formatMessage(messages.deleteConfirm),
+ // onConfirm: () => {
+ // dispatch(deleteList(id));
+ // history.push('/lists');
+ // },
+ // }));
+ // };
+
+ const title = list ? list.get('title') : id;
+
+ if (typeof list === 'undefined') {
+ return (
+
+
+
+
+
+ );
+ } else if (list === false) {
+ return (
+
+ );
+ }
+
+ const emptyMessage = (
+
+
+
+
+
+ );
+
+ return (
+
+ {/*
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+
+
+ );
+};
+
+export default ListTimeline;
diff --git a/app/soapbox/features/lists/components/new_list_form.js b/app/soapbox/features/lists/components/new_list_form.js
deleted file mode 100644
index a065f0b70..000000000
--- a/app/soapbox/features/lists/components/new_list_form.js
+++ /dev/null
@@ -1,82 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import { defineMessages, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { changeListEditorTitle, submitListEditor } from '../../../actions/lists';
-import { Button } from '../../../components/ui';
-
-const messages = defineMessages({
- label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
- title: { id: 'lists.new.create', defaultMessage: 'Add list' },
- create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
-});
-
-const mapStateToProps = state => ({
- value: state.getIn(['listEditor', 'title']),
- disabled: state.getIn(['listEditor', 'isSubmitting']),
-});
-
-const mapDispatchToProps = dispatch => ({
- onChange: value => dispatch(changeListEditorTitle(value)),
- onSubmit: () => dispatch(submitListEditor(true)),
-});
-
-export default @connect(mapStateToProps, mapDispatchToProps)
-@injectIntl
-class NewListForm extends React.PureComponent {
-
- static propTypes = {
- value: PropTypes.string.isRequired,
- disabled: PropTypes.bool,
- intl: PropTypes.object.isRequired,
- onChange: PropTypes.func.isRequired,
- onSubmit: PropTypes.func.isRequired,
- };
-
- handleChange = e => {
- this.props.onChange(e.target.value);
- }
-
- handleSubmit = e => {
- e.preventDefault();
- this.props.onSubmit();
- }
-
- handleClick = e => {
- e.preventDefault();
- this.props.onSubmit();
- }
-
- render() {
- const { value, disabled, intl } = this.props;
-
- const label = intl.formatMessage(messages.label);
- const create = intl.formatMessage(messages.create);
-
- return (
-
- );
- }
-
-}
diff --git a/app/soapbox/features/lists/components/new_list_form.tsx b/app/soapbox/features/lists/components/new_list_form.tsx
new file mode 100644
index 000000000..b6385ecf3
--- /dev/null
+++ b/app/soapbox/features/lists/components/new_list_form.tsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import { defineMessages, useIntl } from 'react-intl';
+import { useDispatch } from 'react-redux';
+
+import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists';
+import { Button } from 'soapbox/components/ui';
+import { useAppSelector } from 'soapbox/hooks';
+
+const messages = defineMessages({
+ label: { id: 'lists.new.title_placeholder', defaultMessage: 'New list title' },
+ title: { id: 'lists.new.create', defaultMessage: 'Add list' },
+ create: { id: 'lists.new.create_title', defaultMessage: 'Create' },
+});
+
+const NewListForm: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const value = useAppSelector((state) => state.listEditor.get('title'));
+ const disabled = useAppSelector((state) => !!state.listEditor.get('isSubmitting'));
+
+ const handleChange = (e: React.ChangeEvent) => {
+ dispatch(changeListEditorTitle(e.target.value));
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ dispatch(submitListEditor(true));
+ };
+
+ const label = intl.formatMessage(messages.label);
+ const create = intl.formatMessage(messages.create);
+
+ return (
+
+ );
+};
+
+export default NewListForm;
diff --git a/app/soapbox/features/lists/index.js b/app/soapbox/features/lists/index.js
deleted file mode 100644
index 03521286d..000000000
--- a/app/soapbox/features/lists/index.js
+++ /dev/null
@@ -1,89 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-import { createSelector } from 'reselect';
-
-import { fetchLists } from 'soapbox/actions/lists';
-import ScrollableList from 'soapbox/components/scrollable_list';
-import { Spinner } from 'soapbox/components/ui';
-import { CardHeader, CardTitle } from 'soapbox/components/ui';
-
-
-import Column from '../ui/components/column';
-import ColumnLink from '../ui/components/column_link';
-
-import NewListForm from './components/new_list_form';
-
-const messages = defineMessages({
- heading: { id: 'column.lists', defaultMessage: 'Lists' },
- subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
- add: { id: 'lists.new.create', defaultMessage: 'Add list' },
-});
-
-const getOrderedLists = createSelector([state => state.get('lists')], lists => {
- if (!lists) {
- return lists;
- }
-
- return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
-});
-
-const mapStateToProps = state => ({
- lists: getOrderedLists(state),
-});
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Lists extends ImmutablePureComponent {
-
- static propTypes = {
- params: PropTypes.object.isRequired,
- dispatch: PropTypes.func.isRequired,
- lists: ImmutablePropTypes.list,
- intl: PropTypes.object.isRequired,
- };
-
- componentDidMount() {
- this.props.dispatch(fetchLists());
- }
-
- render() {
- const { intl, lists } = this.props;
-
- if (!lists) {
- return (
-
-
-
- );
- }
-
- const emptyMessage = ;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- {lists.map(list =>
- ,
- )}
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/lists/index.tsx b/app/soapbox/features/lists/index.tsx
new file mode 100644
index 000000000..5b9a0e936
--- /dev/null
+++ b/app/soapbox/features/lists/index.tsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import { useEffect } from 'react';
+import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
+import { useDispatch } from 'react-redux';
+import { createSelector } from 'reselect';
+
+import { fetchLists } from 'soapbox/actions/lists';
+import ScrollableList from 'soapbox/components/scrollable_list';
+import { Spinner } from 'soapbox/components/ui';
+import { CardHeader, CardTitle } from 'soapbox/components/ui';
+import { useAppSelector } from 'soapbox/hooks';
+
+import Column from '../ui/components/column';
+import ColumnLink from '../ui/components/column_link';
+
+import NewListForm from './components/new_list_form';
+
+import type { RootState } from 'soapbox/store';
+
+const messages = defineMessages({
+ heading: { id: 'column.lists', defaultMessage: 'Lists' },
+ subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
+ add: { id: 'lists.new.create', defaultMessage: 'Add list' },
+});
+
+const getOrderedLists = createSelector([(state: RootState) => state.lists], lists => {
+ if (!lists) {
+ return lists;
+ }
+
+ return lists.toList().filter((item) => !!item).sort((a: any, b: any) => a.get('title').localeCompare(b.get('title')));
+});
+
+const Lists: React.FC = () => {
+ const dispatch = useDispatch();
+ const intl = useIntl();
+
+ const lists = useAppSelector((state) => getOrderedLists(state));
+
+ useEffect(() => {
+ dispatch(fetchLists());
+ }, []);
+
+ if (!lists) {
+ return (
+
+
+
+ );
+ }
+
+ const emptyMessage = ;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ {lists.map((list: any) =>
+ ,
+ )}
+
+
+ );
+};
+
+export default Lists;
diff --git a/app/soapbox/normalizers/index.ts b/app/soapbox/normalizers/index.ts
index 3251669de..b6d3ba8de 100644
--- a/app/soapbox/normalizers/index.ts
+++ b/app/soapbox/normalizers/index.ts
@@ -5,6 +5,7 @@ export { ChatRecord, normalizeChat } from './chat';
export { ChatMessageRecord, normalizeChatMessage } from './chat_message';
export { EmojiRecord, normalizeEmoji } from './emoji';
export { InstanceRecord, normalizeInstance } from './instance';
+export { ListRecord, normalizeList } from './list';
export { MentionRecord, normalizeMention } from './mention';
export { NotificationRecord, normalizeNotification } from './notification';
export { PollRecord, PollOptionRecord, normalizePoll } from './poll';
diff --git a/app/soapbox/normalizers/list.ts b/app/soapbox/normalizers/list.ts
new file mode 100644
index 000000000..ed3ed1c94
--- /dev/null
+++ b/app/soapbox/normalizers/list.ts
@@ -0,0 +1,19 @@
+/**
+ * List normalizer:
+ * Converts API lists into our internal format.
+ * @see {@link https://docs.joinmastodon.org/entities/list/}
+ */
+import { Record as ImmutableRecord, Map as ImmutableMap, fromJS } from 'immutable';
+
+// https://docs.joinmastodon.org/entities/list/
+export const ListRecord = ImmutableRecord({
+ id: '',
+ title: '',
+ replies_policy: null as 'followed' | 'list' | 'none' | null,
+});
+
+export const normalizeList = (list: Record) => {
+ return ListRecord(
+ ImmutableMap(fromJS(list)),
+ );
+};
diff --git a/app/soapbox/reducers/list_adder.js b/app/soapbox/reducers/list_adder.ts
similarity index 51%
rename from app/soapbox/reducers/list_adder.js
rename to app/soapbox/reducers/list_adder.ts
index 0f61273aa..c045a4d53 100644
--- a/app/soapbox/reducers/list_adder.js
+++ b/app/soapbox/reducers/list_adder.ts
@@ -1,4 +1,5 @@
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
+import { AnyAction } from 'redux';
import {
LIST_ADDER_RESET,
@@ -10,20 +11,24 @@ import {
LIST_EDITOR_REMOVE_SUCCESS,
} from '../actions/lists';
-const initialState = ImmutableMap({
- accountId: null,
-
- lists: ImmutableMap({
- items: ImmutableList(),
- loaded: false,
- isLoading: false,
- }),
+const ListsRecord = ImmutableRecord({
+ items: ImmutableList(),
+ loaded: false,
+ isLoading: false,
});
-export default function listAdderReducer(state = initialState, action) {
+const ReducerRecord = ImmutableRecord({
+ accountId: null as string | null,
+
+ lists: ListsRecord(),
+});
+
+type State = ReturnType;
+
+export default function listAdderReducer(state: State = ReducerRecord(), action: AnyAction) {
switch(action.type) {
case LIST_ADDER_RESET:
- return initialState;
+ return ReducerRecord();
case LIST_ADDER_SETUP:
return state.withMutations(map => {
map.set('accountId', action.account.get('id'));
@@ -36,12 +41,12 @@ export default function listAdderReducer(state = initialState, action) {
return state.update('lists', lists => lists.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
- map.set('items', ImmutableList(action.lists.map(item => item.id)));
+ map.set('items', ImmutableList(action.lists.map((item: { id: string }) => item.id)));
}));
case LIST_EDITOR_ADD_SUCCESS:
- return state.updateIn(['lists', 'items'], list => list.unshift(action.listId));
+ return state.updateIn(['lists', 'items'], list => (list as ImmutableList).unshift(action.listId));
case LIST_EDITOR_REMOVE_SUCCESS:
- return state.updateIn(['lists', 'items'], list => list.filterNot(item => item === action.listId));
+ return state.updateIn(['lists', 'items'], list => (list as ImmutableList).filterNot(item => item === action.listId));
default:
return state;
}
diff --git a/app/soapbox/reducers/list_editor.js b/app/soapbox/reducers/list_editor.ts
similarity index 68%
rename from app/soapbox/reducers/list_editor.js
rename to app/soapbox/reducers/list_editor.ts
index ceceb27c7..3c1611a92 100644
--- a/app/soapbox/reducers/list_editor.js
+++ b/app/soapbox/reducers/list_editor.ts
@@ -1,4 +1,5 @@
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
+import { AnyAction } from 'redux';
import {
LIST_CREATE_REQUEST,
@@ -20,28 +21,34 @@ import {
LIST_EDITOR_REMOVE_SUCCESS,
} from '../actions/lists';
-const initialState = ImmutableMap({
- listId: null,
+const AccountsRecord = ImmutableRecord({
+ items: ImmutableList(),
+ loaded: false,
+ isLoading: false,
+});
+
+const SuggestionsRecord = ImmutableRecord({
+ value: '',
+ items: ImmutableList(),
+});
+
+const ReducerRecord = ImmutableRecord({
+ listId: null as string | null,
isSubmitting: false,
isChanged: false,
title: '',
- accounts: ImmutableMap({
- items: ImmutableList(),
- loaded: false,
- isLoading: false,
- }),
+ accounts: AccountsRecord(),
- suggestions: ImmutableMap({
- value: '',
- items: ImmutableList(),
- }),
+ suggestions: SuggestionsRecord(),
});
-export default function listEditorReducer(state = initialState, action) {
+type State = ReturnType;
+
+export default function listEditorReducer(state: State = ReducerRecord(), action: AnyAction) {
switch(action.type) {
case LIST_EDITOR_RESET:
- return initialState;
+ return ReducerRecord();
case LIST_EDITOR_SETUP:
return state.withMutations(map => {
map.set('listId', action.list.get('id'));
@@ -76,21 +83,21 @@ export default function listEditorReducer(state = initialState, action) {
return state.update('accounts', accounts => accounts.withMutations(map => {
map.set('isLoading', false);
map.set('loaded', true);
- map.set('items', ImmutableList(action.accounts.map(item => item.id)));
+ map.set('items', ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
}));
case LIST_EDITOR_SUGGESTIONS_CHANGE:
return state.setIn(['suggestions', 'value'], action.value);
case LIST_EDITOR_SUGGESTIONS_READY:
- return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map(item => item.id)));
+ return state.setIn(['suggestions', 'items'], ImmutableList(action.accounts.map((item: { id: string }) => item.id)));
case LIST_EDITOR_SUGGESTIONS_CLEAR:
return state.update('suggestions', suggestions => suggestions.withMutations(map => {
map.set('items', ImmutableList());
map.set('value', '');
}));
case LIST_EDITOR_ADD_SUCCESS:
- return state.updateIn(['accounts', 'items'], list => list.unshift(action.accountId));
+ return state.updateIn(['accounts', 'items'], list => (list as ImmutableList).unshift(action.accountId));
case LIST_EDITOR_REMOVE_SUCCESS:
- return state.updateIn(['accounts', 'items'], list => list.filterNot(item => item === action.accountId));
+ return state.updateIn(['accounts', 'items'], list => (list as ImmutableList).filterNot((item) => item === action.accountId));
default:
return state;
}
diff --git a/app/soapbox/reducers/lists.js b/app/soapbox/reducers/lists.js
deleted file mode 100644
index 2a797772b..000000000
--- a/app/soapbox/reducers/lists.js
+++ /dev/null
@@ -1,38 +0,0 @@
-import { Map as ImmutableMap, fromJS } from 'immutable';
-
-import {
- LIST_FETCH_SUCCESS,
- LIST_FETCH_FAIL,
- LISTS_FETCH_SUCCESS,
- LIST_CREATE_SUCCESS,
- LIST_UPDATE_SUCCESS,
- LIST_DELETE_SUCCESS,
-} from '../actions/lists';
-
-const initialState = ImmutableMap();
-
-const normalizeList = (state, list) => state.set(list.id, fromJS(list));
-
-const normalizeLists = (state, lists) => {
- lists.forEach(list => {
- state = normalizeList(state, list);
- });
-
- return state;
-};
-
-export default function lists(state = initialState, action) {
- switch(action.type) {
- case LIST_FETCH_SUCCESS:
- case LIST_CREATE_SUCCESS:
- case LIST_UPDATE_SUCCESS:
- return normalizeList(state, action.list);
- case LISTS_FETCH_SUCCESS:
- return normalizeLists(state, action.lists);
- case LIST_DELETE_SUCCESS:
- case LIST_FETCH_FAIL:
- return state.set(action.id, false);
- default:
- return state;
- }
-}
diff --git a/app/soapbox/reducers/lists.ts b/app/soapbox/reducers/lists.ts
new file mode 100644
index 000000000..e0ab4db97
--- /dev/null
+++ b/app/soapbox/reducers/lists.ts
@@ -0,0 +1,46 @@
+import { Map as ImmutableMap } from 'immutable';
+import { AnyAction } from 'redux';
+
+import {
+ LIST_FETCH_SUCCESS,
+ LIST_FETCH_FAIL,
+ LISTS_FETCH_SUCCESS,
+ LIST_CREATE_SUCCESS,
+ LIST_UPDATE_SUCCESS,
+ LIST_DELETE_SUCCESS,
+} from 'soapbox/actions/lists';
+import { normalizeList } from 'soapbox/normalizers';
+
+type ListRecord = ReturnType;
+type APIEntity = Record;
+type APIEntities = Array;
+
+type State = ImmutableMap;
+
+const initialState: State = ImmutableMap();
+
+const importList = (state: State, list: APIEntity) => state.set(list.id, normalizeList(list));
+
+const importLists = (state: State, lists: APIEntities) => {
+ lists.forEach(list => {
+ state = importList(state, list);
+ });
+
+ return state;
+};
+
+export default function lists(state: State = initialState, action: AnyAction) {
+ switch(action.type) {
+ case LIST_FETCH_SUCCESS:
+ case LIST_CREATE_SUCCESS:
+ case LIST_UPDATE_SUCCESS:
+ return importList(state, action.list);
+ case LISTS_FETCH_SUCCESS:
+ return importLists(state, action.lists);
+ case LIST_DELETE_SUCCESS:
+ case LIST_FETCH_FAIL:
+ return state.set(action.id, false);
+ default:
+ return state;
+ }
+}