kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Reducers: TypeScript, fixes
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>environments/review-develop-3zknud/deployments/194^2
rodzic
5bb26c9b47
commit
41a2b1f08f
|
@ -249,7 +249,7 @@ export interface IDropdown extends RouteComponentProps {
|
||||||
) => void,
|
) => void,
|
||||||
onClose?: (id: number) => void,
|
onClose?: (id: number) => void,
|
||||||
dropdownPlacement?: string,
|
dropdownPlacement?: string,
|
||||||
openDropdownId?: number,
|
openDropdownId?: number | null,
|
||||||
openedViaKeyboard?: boolean,
|
openedViaKeyboard?: boolean,
|
||||||
text?: string,
|
text?: string,
|
||||||
onShiftClick?: React.EventHandler<React.MouseEvent | React.KeyboardEvent>,
|
onShiftClick?: React.EventHandler<React.MouseEvent | React.KeyboardEvent>,
|
||||||
|
|
|
@ -7,8 +7,6 @@ import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
import Column from '../ui/components/column';
|
import Column from '../ui/components/column';
|
||||||
|
|
||||||
import type { Map as ImmutableMap } from 'immutable';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation Log' },
|
heading: { id: 'column.admin.moderation_log', defaultMessage: 'Moderation Log' },
|
||||||
emptyMessage: { id: 'admin.moderation_log.empty_message', defaultMessage: 'You have not performed any moderation actions yet. When you do, a history will be shown here.' },
|
emptyMessage: { id: 'admin.moderation_log.empty_message', defaultMessage: 'You have not performed any moderation actions yet. When you do, a history will be shown here.' },
|
||||||
|
@ -18,8 +16,10 @@ const ModerationLog = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const items = useAppSelector((state) => state.admin_log.get('index').map((i: number) => state.admin_log.getIn(['items', String(i)]))) as ImmutableMap<string, any>;
|
const items = useAppSelector((state) => {
|
||||||
const hasMore = useAppSelector((state) => state.admin_log.get('total', 0) - state.admin_log.get('index').count() > 0);
|
return state.admin_log.index.map((i) => state.admin_log.items.get(String(i)));
|
||||||
|
});
|
||||||
|
const hasMore = useAppSelector((state) => state.admin_log.total - state.admin_log.index.count() > 0);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [lastPage, setLastPage] = useState(0);
|
const [lastPage, setLastPage] = useState(0);
|
||||||
|
@ -56,12 +56,12 @@ const ModerationLog = () => {
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
>
|
>
|
||||||
{items.map((item, i) => (
|
{items.map((item) => item && (
|
||||||
<div className='logentry' key={i}>
|
<div className='logentry' key={item.id}>
|
||||||
<div className='logentry__message'>{item.get('message')}</div>
|
<div className='logentry__message'>{item.message}</div>
|
||||||
<div className='logentry__timestamp'>
|
<div className='logentry__timestamp'>
|
||||||
<FormattedDate
|
<FormattedDate
|
||||||
value={new Date(item.get('time') * 1000)}
|
value={new Date(item.time * 1000)}
|
||||||
hour12={false}
|
hour12={false}
|
||||||
year='numeric'
|
year='numeric'
|
||||||
month='short'
|
month='short'
|
||||||
|
|
|
@ -20,9 +20,9 @@ const Bookmarks: React.FC = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const statusIds = useAppSelector((state) => state.status_lists.getIn(['bookmarks', 'items']));
|
const statusIds = useAppSelector((state) => state.status_lists.get('bookmarks')!.items);
|
||||||
const isLoading = useAppSelector((state) => state.status_lists.getIn(['bookmarks', 'isLoading'], true));
|
const isLoading = useAppSelector((state) => state.status_lists.get('bookmarks')!.isLoading);
|
||||||
const hasMore = useAppSelector((state) => !!state.status_lists.getIn(['bookmarks', 'next']));
|
const hasMore = useAppSelector((state) => !!state.status_lists.get('bookmarks')!.next);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
dispatch(fetchBookmarkedStatuses());
|
dispatch(fetchBookmarkedStatuses());
|
||||||
|
@ -43,7 +43,7 @@ const Bookmarks: React.FC = () => {
|
||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
scrollKey='bookmarked_statuses'
|
scrollKey='bookmarked_statuses'
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
isLoading={isLoading}
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
onLoadMore={() => handleLoadMore(dispatch)}
|
onLoadMore={() => handleLoadMore(dispatch)}
|
||||||
onRefresh={handleRefresh}
|
onRefresh={handleRefresh}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
|
|
|
@ -32,9 +32,9 @@ const mapStateToProps = (state, { params }) => {
|
||||||
if (isMyAccount) {
|
if (isMyAccount) {
|
||||||
return {
|
return {
|
||||||
isMyAccount,
|
isMyAccount,
|
||||||
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
|
statusIds: state.status_lists.get('favourites').items,
|
||||||
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
|
isLoading: state.status_lists.get('favourites').isLoading,
|
||||||
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
|
hasMore: !!state.status_lists.get('favourites').next,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,9 +57,9 @@ const mapStateToProps = (state, { params }) => {
|
||||||
unavailable,
|
unavailable,
|
||||||
username,
|
username,
|
||||||
isAccount: !!state.getIn(['accounts', accountId]),
|
isAccount: !!state.getIn(['accounts', accountId]),
|
||||||
statusIds: state.getIn(['status_lists', `favourites:${accountId}`, 'items'], []),
|
statusIds: state.status_lists.get(`favourites:${accountId}`)?.items || [],
|
||||||
isLoading: state.getIn(['status_lists', `favourites:${accountId}`, 'isLoading'], true),
|
isLoading: state.status_lists.get(`favourites:${accountId}`)?.isLoading,
|
||||||
hasMore: !!state.getIn(['status_lists', `favourites:${accountId}`, 'next']),
|
hasMore: !!state.status_lists.get(`favourites:${accountId}`)?.next,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
statusIds={statusIds}
|
statusIds={statusIds}
|
||||||
scrollKey='favourited_statuses'
|
scrollKey='favourited_statuses'
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
isLoading={isLoading}
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
onLoadMore={this.handleLoadMore}
|
onLoadMore={this.handleLoadMore}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -11,8 +11,8 @@ import Account from './account';
|
||||||
const FollowRecommendationsList: React.FC = () => {
|
const FollowRecommendationsList: React.FC = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const suggestions = useAppSelector((state) => state.suggestions.get('items'));
|
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||||
const isLoading = useAppSelector((state) => state.suggestions.get('isLoading'));
|
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (suggestions.size === 0) {
|
if (suggestions.size === 0) {
|
||||||
|
@ -30,8 +30,8 @@ const FollowRecommendationsList: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='column-list'>
|
<div className='column-list'>
|
||||||
{suggestions.size > 0 ? suggestions.map((suggestion: { account: string }, idx: number) => (
|
{suggestions.size > 0 ? suggestions.map((suggestion) => (
|
||||||
<Account key={idx} id={suggestion.account} />
|
<Account key={suggestion.account} id={suggestion.account} />
|
||||||
)) : (
|
)) : (
|
||||||
<div className='column-list__empty-message'>
|
<div className='column-list__empty-message'>
|
||||||
<FormattedMessage id='empty_column.follow_recommendations' defaultMessage='Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.' />
|
<FormattedMessage id='empty_column.follow_recommendations' defaultMessage='Looks like no suggestions could be generated for you. You can try using search to look for people you might know or explore trending hashtags.' />
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
@ -13,9 +12,9 @@ import { useAppSelector } from 'soapbox/hooks';
|
||||||
const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const suggestions = useAppSelector((state) => state.suggestions.get('items'));
|
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||||
const hasMore = useAppSelector((state) => !!state.suggestions.get('next'));
|
const hasMore = useAppSelector((state) => !!state.suggestions.next);
|
||||||
const isLoading = useAppSelector((state) => state.suggestions.get('isLoading'));
|
const isLoading = useAppSelector((state) => state.suggestions.isLoading);
|
||||||
|
|
||||||
const handleLoadMore = debounce(() => {
|
const handleLoadMore = debounce(() => {
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
|
@ -40,11 +39,11 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
||||||
useWindowScroll={false}
|
useWindowScroll={false}
|
||||||
style={{ height: 320 }}
|
style={{ height: 320 }}
|
||||||
>
|
>
|
||||||
{suggestions.map((suggestion: ImmutableMap<string, any>) => (
|
{suggestions.map((suggestion) => (
|
||||||
<div key={suggestion.get('account')} className='py-2'>
|
<div key={suggestion.account} className='py-2'>
|
||||||
<AccountContainer
|
<AccountContainer
|
||||||
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
||||||
id={suggestion.get('account')}
|
id={suggestion.account}
|
||||||
showProfileHoverCard={false}
|
showProfileHoverCard={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -21,8 +21,8 @@ const mapStateToProps = (state, { params }) => {
|
||||||
const meUsername = state.getIn(['accounts', me, 'username'], '');
|
const meUsername = state.getIn(['accounts', me, 'username'], '');
|
||||||
return {
|
return {
|
||||||
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
|
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
|
||||||
statusIds: state.getIn(['status_lists', 'pins', 'items']),
|
statusIds: state.status_lists.get('pins').items,
|
||||||
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
|
hasMore: !!state.status_lists.get('pins').next,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -22,9 +22,9 @@ const ScheduledStatuses = () => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
const statusIds = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'items']));
|
const statusIds = useAppSelector((state) => state.status_lists.get('scheduled_statuses')!.items);
|
||||||
const isLoading = useAppSelector((state) => state.status_lists.getIn(['scheduled_statuses', 'isLoading']));
|
const isLoading = useAppSelector((state) => state.status_lists.get('scheduled_statuses')!.isLoading);
|
||||||
const hasMore = useAppSelector((state) => !!state.status_lists.getIn(['scheduled_statuses', 'next']));
|
const hasMore = useAppSelector((state) => !!state.status_lists.get('scheduled_statuses')!.next);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchScheduledStatuses());
|
dispatch(fetchScheduledStatuses());
|
||||||
|
@ -37,7 +37,7 @@ const ScheduledStatuses = () => {
|
||||||
<ScrollableList
|
<ScrollableList
|
||||||
scrollKey='scheduled_statuses'
|
scrollKey='scheduled_statuses'
|
||||||
hasMore={hasMore}
|
hasMore={hasMore}
|
||||||
isLoading={isLoading}
|
isLoading={typeof isLoading === 'boolean' ? isLoading : true}
|
||||||
onLoadMore={() => handleLoadMore(dispatch)}
|
onLoadMore={() => handleLoadMore(dispatch)}
|
||||||
emptyMessage={emptyMessage}
|
emptyMessage={emptyMessage}
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { render, screen } from '../../../../jest/test-helpers';
|
import { render, screen } from '../../../../jest/test-helpers';
|
||||||
|
@ -16,12 +16,12 @@ describe('<WhoToFollow />', () => {
|
||||||
avatar: 'test.jpg',
|
avatar: 'test.jpg',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
suggestions: ImmutableMap({
|
suggestions: {
|
||||||
items: fromJS([{
|
items: ImmutableOrderedSet([{
|
||||||
source: 'staff',
|
source: 'staff',
|
||||||
account: '1',
|
account: '1',
|
||||||
}]),
|
}]),
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<WhoToFollowPanel limit={1} />, null, store);
|
render(<WhoToFollowPanel limit={1} />, null, store);
|
||||||
|
@ -44,8 +44,8 @@ describe('<WhoToFollow />', () => {
|
||||||
avatar: 'test.jpg',
|
avatar: 'test.jpg',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
suggestions: ImmutableMap({
|
suggestions: {
|
||||||
items: fromJS([
|
items: ImmutableOrderedSet([
|
||||||
{
|
{
|
||||||
source: 'staff',
|
source: 'staff',
|
||||||
account: '1',
|
account: '1',
|
||||||
|
@ -55,7 +55,7 @@ describe('<WhoToFollow />', () => {
|
||||||
account: '2',
|
account: '2',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<WhoToFollowPanel limit={3} />, null, store);
|
render(<WhoToFollowPanel limit={3} />, null, store);
|
||||||
|
@ -78,8 +78,8 @@ describe('<WhoToFollow />', () => {
|
||||||
avatar: 'test.jpg',
|
avatar: 'test.jpg',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
suggestions: ImmutableMap({
|
suggestions: {
|
||||||
items: fromJS([
|
items: ImmutableOrderedSet([
|
||||||
{
|
{
|
||||||
source: 'staff',
|
source: 'staff',
|
||||||
account: '1',
|
account: '1',
|
||||||
|
@ -89,7 +89,7 @@ describe('<WhoToFollow />', () => {
|
||||||
account: '2',
|
account: '2',
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<WhoToFollowPanel limit={1} />, null, store);
|
render(<WhoToFollowPanel limit={1} />, null, store);
|
||||||
|
@ -112,9 +112,9 @@ describe('<WhoToFollow />', () => {
|
||||||
avatar: 'test.jpg',
|
avatar: 'test.jpg',
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
suggestions: ImmutableMap({
|
suggestions: {
|
||||||
items: fromJS([]),
|
items: ImmutableOrderedSet([]),
|
||||||
}),
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
render(<WhoToFollowPanel limit={1} />, null, store);
|
render(<WhoToFollowPanel limit={1} />, null, store);
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { Map as ImmutableMap } from 'immutable';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { fetchSuggestions, dismissSuggestion } from 'soapbox/actions/suggestions';
|
import { fetchSuggestions, dismissSuggestion } from 'soapbox/actions/suggestions';
|
||||||
|
@ -8,6 +7,8 @@ import { Widget } from 'soapbox/components/ui';
|
||||||
import AccountContainer from 'soapbox/containers/account_container';
|
import AccountContainer from 'soapbox/containers/account_container';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
||||||
});
|
});
|
||||||
|
@ -20,11 +21,11 @@ const WhoToFollowPanel = ({ limit }: IWhoToFollowPanel) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const suggestions = useAppSelector((state) => state.suggestions.get('items'));
|
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||||
const suggestionsToRender = suggestions.slice(0, limit);
|
const suggestionsToRender = suggestions.slice(0, limit);
|
||||||
|
|
||||||
const handleDismiss = (account: ImmutableMap<string, any>) => {
|
const handleDismiss = (account: AccountEntity) => {
|
||||||
dispatch(dismissSuggestion(account.get('id')));
|
dispatch(dismissSuggestion(account.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
|
@ -45,11 +46,11 @@ const WhoToFollowPanel = ({ limit }: IWhoToFollowPanel) => {
|
||||||
title={<FormattedMessage id='who_to_follow.title' defaultMessage='People To Follow' />}
|
title={<FormattedMessage id='who_to_follow.title' defaultMessage='People To Follow' />}
|
||||||
// onAction={handleAction}
|
// onAction={handleAction}
|
||||||
>
|
>
|
||||||
{suggestionsToRender.map((suggestion: ImmutableMap<string, any>) => (
|
{suggestionsToRender.map((suggestion) => (
|
||||||
<AccountContainer
|
<AccountContainer
|
||||||
key={suggestion.get('account')}
|
key={suggestion.account}
|
||||||
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
||||||
id={suggestion.get('account')}
|
id={suggestion.account}
|
||||||
actionIcon={require('@tabler/icons/icons/x.svg')}
|
actionIcon={require('@tabler/icons/icons/x.svg')}
|
||||||
actionTitle={intl.formatMessage(messages.dismissSuggestion)}
|
actionTitle={intl.formatMessage(messages.dismissSuggestion)}
|
||||||
onActionClick={handleDismiss}
|
onActionClick={handleDismiss}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
|
||||||
|
|
||||||
import reducer from '../status_lists';
|
|
||||||
|
|
||||||
describe('status_lists reducer', () => {
|
|
||||||
it('should return the initial state', () => {
|
|
||||||
expect(reducer(undefined, {})).toEqual(ImmutableMap({
|
|
||||||
favourites: ImmutableMap({
|
|
||||||
next: null,
|
|
||||||
loaded: false,
|
|
||||||
items: ImmutableOrderedSet(),
|
|
||||||
}),
|
|
||||||
bookmarks: ImmutableMap({
|
|
||||||
next: null,
|
|
||||||
loaded: false,
|
|
||||||
items: ImmutableOrderedSet(),
|
|
||||||
}),
|
|
||||||
pins: ImmutableMap({
|
|
||||||
next: null,
|
|
||||||
loaded: false,
|
|
||||||
items: ImmutableOrderedSet(),
|
|
||||||
}),
|
|
||||||
scheduled_statuses: ImmutableMap({
|
|
||||||
next: null,
|
|
||||||
loaded: false,
|
|
||||||
items: ImmutableOrderedSet(),
|
|
||||||
}),
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import reducer from '../status_lists';
|
||||||
|
|
||||||
|
describe('status_lists reducer', () => {
|
||||||
|
it('should return the initial state', () => {
|
||||||
|
expect(reducer(undefined, {} as any).toJS()).toEqual({
|
||||||
|
favourites: {
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
isLoading: null,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
bookmarks: {
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
isLoading: null,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
pins: {
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
isLoading: null,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
scheduled_statuses: {
|
||||||
|
next: null,
|
||||||
|
loaded: false,
|
||||||
|
isLoading: null,
|
||||||
|
items: [],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,30 +2,37 @@ import {
|
||||||
Map as ImmutableMap,
|
Map as ImmutableMap,
|
||||||
Record as ImmutableRecord,
|
Record as ImmutableRecord,
|
||||||
OrderedSet as ImmutableOrderedSet,
|
OrderedSet as ImmutableOrderedSet,
|
||||||
fromJS,
|
|
||||||
} from 'immutable';
|
} from 'immutable';
|
||||||
|
|
||||||
import { ADMIN_LOG_FETCH_SUCCESS } from 'soapbox/actions/admin';
|
import { ADMIN_LOG_FETCH_SUCCESS } from 'soapbox/actions/admin';
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
|
|
||||||
|
const LogEntryRecord = ImmutableRecord({
|
||||||
|
data: ImmutableMap<string, any>(),
|
||||||
|
id: 0,
|
||||||
|
message: '',
|
||||||
|
time: 0,
|
||||||
|
});
|
||||||
|
|
||||||
const ReducerRecord = ImmutableRecord({
|
const ReducerRecord = ImmutableRecord({
|
||||||
items: ImmutableMap(),
|
items: ImmutableMap<string, LogEntry>(),
|
||||||
index: ImmutableOrderedSet(),
|
index: ImmutableOrderedSet<number>(),
|
||||||
total: 0,
|
total: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type LogEntry = ReturnType<typeof LogEntryRecord>;
|
||||||
type State = ReturnType<typeof ReducerRecord>;
|
type State = ReturnType<typeof ReducerRecord>;
|
||||||
type APIEntity = Record<string, any>;
|
type APIEntity = Record<string, any>;
|
||||||
type APIEntities = Array<APIEntity>;
|
type APIEntities = Array<APIEntity>;
|
||||||
|
|
||||||
const parseItems = (items: APIEntities) => {
|
const parseItems = (items: APIEntities) => {
|
||||||
const ids: Array<number> = [];
|
const ids: Array<number> = [];
|
||||||
const map: Record<number, any> = {};
|
const map: Record<string, LogEntry> = {};
|
||||||
|
|
||||||
items.forEach(item => {
|
items.forEach(item => {
|
||||||
ids.push(item.id);
|
ids.push(item.id);
|
||||||
map[item.id] = item;
|
map[item.id] = LogEntryRecord(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { ids: ids, map: map };
|
return { ids: ids, map: map };
|
||||||
|
@ -36,7 +43,7 @@ const importItems = (state: State, items: APIEntities, total: number) => {
|
||||||
|
|
||||||
return state.withMutations(state => {
|
return state.withMutations(state => {
|
||||||
state.update('index', v => v.union(ids));
|
state.update('index', v => v.union(ids));
|
||||||
state.update('items', v => v.merge(fromJS(map)));
|
state.update('items', v => v.merge(map));
|
||||||
state.set('total', total);
|
state.set('total', total);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,10 +6,11 @@ import {
|
||||||
} from '../actions/dropdown_menu';
|
} from '../actions/dropdown_menu';
|
||||||
|
|
||||||
import type { AnyAction } from 'redux';
|
import type { AnyAction } from 'redux';
|
||||||
|
import type { DropdownPlacement } from 'soapbox/components/dropdown_menu';
|
||||||
|
|
||||||
const ReducerRecord = ImmutableRecord({
|
const ReducerRecord = ImmutableRecord({
|
||||||
openId: null as number | null,
|
openId: null as number | null,
|
||||||
placement: null as 'top' | 'bottom' | null,
|
placement: null as any as DropdownPlacement,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
import {
|
||||||
|
Map as ImmutableMap,
|
||||||
|
OrderedSet as ImmutableOrderedSet,
|
||||||
|
Record as ImmutableRecord,
|
||||||
|
} from 'immutable';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
BOOKMARKED_STATUSES_FETCH_REQUEST,
|
||||||
|
@ -44,29 +48,38 @@ import {
|
||||||
SCHEDULED_STATUS_CANCEL_SUCCESS,
|
SCHEDULED_STATUS_CANCEL_SUCCESS,
|
||||||
} from '../actions/scheduled_statuses';
|
} from '../actions/scheduled_statuses';
|
||||||
|
|
||||||
const initialMap = ImmutableMap({
|
import type { AnyAction } from 'redux';
|
||||||
next: null,
|
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const StatusListRecord = ImmutableRecord({
|
||||||
|
next: null as string | null,
|
||||||
loaded: false,
|
loaded: false,
|
||||||
items: ImmutableOrderedSet(),
|
isLoading: null as boolean | null,
|
||||||
|
items: ImmutableOrderedSet<string>(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
type State = ImmutableMap<string, StatusList>;
|
||||||
favourites: initialMap,
|
type StatusList = ReturnType<typeof StatusListRecord>;
|
||||||
bookmarks: initialMap,
|
type Status = string | StatusEntity;
|
||||||
pins: initialMap,
|
type Statuses = Array<string | StatusEntity>;
|
||||||
scheduled_statuses: initialMap,
|
|
||||||
|
const initialState: State = ImmutableMap({
|
||||||
|
favourites: StatusListRecord(),
|
||||||
|
bookmarks: StatusListRecord(),
|
||||||
|
pins: StatusListRecord(),
|
||||||
|
scheduled_statuses: StatusListRecord(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const getStatusId = status => typeof status === 'string' ? status : status.get('id');
|
const getStatusId = (status: string | StatusEntity) => typeof status === 'string' ? status : status.id;
|
||||||
|
|
||||||
const getStatusIds = (statuses = []) => (
|
const getStatusIds = (statuses: Statuses = []) => (
|
||||||
ImmutableOrderedSet(statuses.map(status => status.id))
|
ImmutableOrderedSet(statuses.map(getStatusId))
|
||||||
);
|
);
|
||||||
|
|
||||||
const setLoading = (state, listType, loading) => state.setIn([listType, 'isLoading'], loading);
|
const setLoading = (state: State, listType: string, loading: boolean) => state.setIn([listType, 'isLoading'], loading);
|
||||||
|
|
||||||
const normalizeList = (state, listType, statuses, next) => {
|
const normalizeList = (state: State, listType: string, statuses: Statuses, next: string | null) => {
|
||||||
return state.update(listType, initialMap, listMap => listMap.withMutations(map => {
|
return state.update(listType, StatusListRecord(), listMap => listMap.withMutations(map => {
|
||||||
map.set('next', next);
|
map.set('next', next);
|
||||||
map.set('loaded', true);
|
map.set('loaded', true);
|
||||||
map.set('isLoading', false);
|
map.set('isLoading', false);
|
||||||
|
@ -74,29 +87,29 @@ const normalizeList = (state, listType, statuses, next) => {
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendToList = (state, listType, statuses, next) => {
|
const appendToList = (state: State, listType: string, statuses: Statuses, next: string | null) => {
|
||||||
const newIds = getStatusIds(statuses);
|
const newIds = getStatusIds(statuses);
|
||||||
|
|
||||||
return state.update(listType, initialMap, listMap => listMap.withMutations(map => {
|
return state.update(listType, StatusListRecord(), listMap => listMap.withMutations(map => {
|
||||||
map.set('next', next);
|
map.set('next', next);
|
||||||
map.set('isLoading', false);
|
map.set('isLoading', false);
|
||||||
map.update('items', ImmutableOrderedSet(), items => items.union(newIds));
|
map.update('items', items => items.union(newIds));
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const prependOneToList = (state, listType, status) => {
|
const prependOneToList = (state: State, listType: string, status: Status) => {
|
||||||
const statusId = getStatusId(status);
|
const statusId = getStatusId(status);
|
||||||
return state.updateIn([listType, 'items'], ImmutableOrderedSet(), items => {
|
return state.updateIn([listType, 'items'], ImmutableOrderedSet(), items => {
|
||||||
return ImmutableOrderedSet([statusId]).union(items);
|
return ImmutableOrderedSet([statusId]).union(items as ImmutableOrderedSet<string>);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeOneFromList = (state, listType, status) => {
|
const removeOneFromList = (state: State, listType: string, status: Status) => {
|
||||||
const statusId = getStatusId(status);
|
const statusId = getStatusId(status);
|
||||||
return state.updateIn([listType, 'items'], ImmutableOrderedSet(), items => items.delete(statusId));
|
return state.updateIn([listType, 'items'], ImmutableOrderedSet(), items => (items as ImmutableOrderedSet<string>).delete(statusId));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function statusLists(state = initialState, action) {
|
export default function statusLists(state = initialState, action: AnyAction) {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case FAVOURITED_STATUSES_FETCH_REQUEST:
|
case FAVOURITED_STATUSES_FETCH_REQUEST:
|
||||||
case FAVOURITED_STATUSES_EXPAND_REQUEST:
|
case FAVOURITED_STATUSES_EXPAND_REQUEST:
|
||||||
|
@ -154,7 +167,7 @@ export default function statusLists(state = initialState, action) {
|
||||||
return appendToList(state, 'scheduled_statuses', action.statuses, action.next);
|
return appendToList(state, 'scheduled_statuses', action.statuses, action.next);
|
||||||
case SCHEDULED_STATUS_CANCEL_REQUEST:
|
case SCHEDULED_STATUS_CANCEL_REQUEST:
|
||||||
case SCHEDULED_STATUS_CANCEL_SUCCESS:
|
case SCHEDULED_STATUS_CANCEL_SUCCESS:
|
||||||
return removeOneFromList(state, 'scheduled_statuses', action.id || action.status.get('id'));
|
return removeOneFromList(state, 'scheduled_statuses', action.id || action.status.id);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
Ładowanie…
Reference in New Issue