TypeScript, FC (reducers, search)
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
bdb958a613
commit
6c45dcb109
|
@ -26,6 +26,8 @@ export interface MenuItem {
|
|||
icon: string,
|
||||
count?: number,
|
||||
destructive?: boolean,
|
||||
meta?: string,
|
||||
active?: boolean,
|
||||
}
|
||||
|
||||
export type Menu = Array<MenuItem | null>;
|
||||
|
|
|
@ -10,24 +10,26 @@ import { shortNumberFormat } from '../utils/numbers';
|
|||
import Permalink from './permalink';
|
||||
import { HStack, Stack, Text } from './ui';
|
||||
|
||||
import type { Map as ImmutableMap } from 'immutable';
|
||||
import type { Hashtag as HashtagEntity } from 'soapbox/reducers/search';
|
||||
import type { TrendingHashtag } from 'soapbox/reducers/trends';
|
||||
|
||||
interface IHashtag {
|
||||
hashtag: ImmutableMap<string, any>,
|
||||
hashtag: HashtagEntity | TrendingHashtag,
|
||||
}
|
||||
|
||||
const Hashtag: React.FC<IHashtag> = ({ hashtag }) => {
|
||||
const count = Number(hashtag.getIn(['history', 0, 'accounts']));
|
||||
const brandColor = useSelector((state) => getSoapboxConfig(state).get('brandColor'));
|
||||
const history = (hashtag as TrendingHashtag).history;
|
||||
const count = Number(history?.get(0)?.accounts);
|
||||
const brandColor = useSelector((state) => getSoapboxConfig(state).brandColor);
|
||||
|
||||
return (
|
||||
<HStack alignItems='center' justifyContent='between' data-testid='hashtag'>
|
||||
<Stack>
|
||||
<Permalink href={hashtag.get('url')} to={`/tags/${hashtag.get('name')}`} className='hover:underline'>
|
||||
<Text tag='span' size='sm' weight='semibold'>#{hashtag.get('name')}</Text>
|
||||
<Permalink href={hashtag.url} to={`/tags/${hashtag.name}`} className='hover:underline'>
|
||||
<Text tag='span' size='sm' weight='semibold'>#{hashtag.name}</Text>
|
||||
</Permalink>
|
||||
|
||||
{hashtag.get('history') && (
|
||||
{history && (
|
||||
<Text theme='muted' size='sm'>
|
||||
<FormattedMessage
|
||||
id='trends.count_by_accounts'
|
||||
|
@ -41,12 +43,12 @@ const Hashtag: React.FC<IHashtag> = ({ hashtag }) => {
|
|||
)}
|
||||
</Stack>
|
||||
|
||||
{hashtag.get('history') && (
|
||||
{history && (
|
||||
<div className='w-[40px]' data-testid='sparklines'>
|
||||
<Sparklines
|
||||
width={40}
|
||||
height={28}
|
||||
data={hashtag.get('history').reverse().map((day: ImmutableMap<string, any>) => day.get('uses')).toArray()}
|
||||
data={history.reverse().map((day) => +day.uses).toArray()}
|
||||
>
|
||||
<SparklinesCurve style={{ fill: 'none' }} color={brandColor} />
|
||||
</Sparklines>
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import PullToRefresh from './pull-to-refresh';
|
||||
|
||||
interface IPullable {
|
||||
children: JSX.Element,
|
||||
children: React.ReactNode,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,7 @@ import { useSoapboxConfig } from 'soapbox/hooks';
|
|||
|
||||
import { Card, CardBody, CardHeader, CardTitle } from '../card/card';
|
||||
|
||||
interface IColumn {
|
||||
export interface IColumn {
|
||||
/** Route the back button goes to. */
|
||||
backHref?: string,
|
||||
/** Column title text. */
|
||||
|
|
|
@ -10,7 +10,7 @@ import type { DropdownPlacement, IDropdown } from 'soapbox/components/dropdown_m
|
|||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isModalOpen: Boolean(state.modals.size && state.modals.last().modalType === 'ACTIONS'),
|
||||
isModalOpen: Boolean(state.modals.size && state.modals.last()!.modalType === 'ACTIONS'),
|
||||
dropdownPlacement: state.dropdown_menu.placement,
|
||||
openDropdownId: state.dropdown_menu.openId,
|
||||
openedViaKeyboard: state.dropdown_menu.keyboard,
|
||||
|
|
|
@ -50,8 +50,8 @@ const Search = (props: ISearch) => {
|
|||
const history = useHistory();
|
||||
const intl = useIntl();
|
||||
|
||||
const value = useAppSelector((state) => state.search.get('value'));
|
||||
const submitted = useAppSelector((state) => state.search.get('submitted'));
|
||||
const value = useAppSelector((state) => state.search.value);
|
||||
const submitted = useAppSelector((state) => state.search.submitted);
|
||||
|
||||
const debouncedSubmit = debounce(() => {
|
||||
dispatch(submitSearch());
|
||||
|
|
|
@ -1,173 +0,0 @@
|
|||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import Hashtag from 'soapbox/components/hashtag';
|
||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||
import { Tabs } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
import StatusContainer from 'soapbox/containers/status_container';
|
||||
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
|
||||
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag';
|
||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
|
||||
|
||||
const messages = defineMessages({
|
||||
accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
|
||||
statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
|
||||
hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class SearchResults extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
results: ImmutablePropTypes.map.isRequired,
|
||||
submitted: PropTypes.bool,
|
||||
expandSearch: PropTypes.func.isRequired,
|
||||
selectedFilter: PropTypes.string.isRequired,
|
||||
selectFilter: PropTypes.func.isRequired,
|
||||
features: PropTypes.object.isRequired,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
trendingStatuses: ImmutablePropTypes.list,
|
||||
trends: ImmutablePropTypes.list,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleLoadMore = () => this.props.expandSearch(this.props.selectedFilter);
|
||||
|
||||
handleSelectFilter = newActiveFilter => this.props.selectFilter(newActiveFilter);
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchTrendingStatuses();
|
||||
}
|
||||
|
||||
renderFilterBar() {
|
||||
const { intl, selectedFilter } = this.props;
|
||||
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.accounts),
|
||||
action: () => this.handleSelectFilter('accounts'),
|
||||
name: 'accounts',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.statuses),
|
||||
action: () => this.handleSelectFilter('statuses'),
|
||||
name: 'statuses',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.hashtags),
|
||||
action: () => this.handleSelectFilter('hashtags'),
|
||||
name: 'hashtags',
|
||||
},
|
||||
];
|
||||
|
||||
return <Tabs items={items} activeItem={selectedFilter} />;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value, results, submitted, selectedFilter, suggestions, trendingStatuses, trends } = this.props;
|
||||
|
||||
let searchResults;
|
||||
let hasMore = false;
|
||||
let loaded;
|
||||
let noResultsMessage;
|
||||
let placeholderComponent = PlaceholderStatus;
|
||||
|
||||
if (selectedFilter === 'accounts') {
|
||||
hasMore = results.get('accountsHasMore');
|
||||
loaded = results.get('accountsLoaded');
|
||||
placeholderComponent = PlaceholderAccount;
|
||||
|
||||
if (results.get('accounts') && results.get('accounts').size > 0) {
|
||||
searchResults = results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />);
|
||||
} else if (!submitted && suggestions && !suggestions.isEmpty()) {
|
||||
searchResults = suggestions.map(suggestion => <AccountContainer key={suggestion.get('account')} id={suggestion.get('account')} />);
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.accounts'
|
||||
defaultMessage='There are no people results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFilter === 'statuses') {
|
||||
hasMore = results.get('statusesHasMore');
|
||||
loaded = results.get('statusesLoaded');
|
||||
|
||||
if (results.get('statuses') && results.get('statuses').size > 0) {
|
||||
searchResults = results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />);
|
||||
} else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) {
|
||||
searchResults = trendingStatuses.map(statusId => <StatusContainer key={statusId} id={statusId} />);
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.statuses'
|
||||
defaultMessage='There are no posts results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFilter === 'hashtags') {
|
||||
hasMore = results.get('hashtagsHasMore');
|
||||
loaded = results.get('hashtagsLoaded');
|
||||
placeholderComponent = PlaceholderHashtag;
|
||||
|
||||
if (results.get('hashtags') && results.get('hashtags').size > 0) {
|
||||
searchResults = results.get('hashtags').map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />);
|
||||
} else if (!submitted && suggestions && !suggestions.isEmpty()) {
|
||||
searchResults = trends.map(hashtag => <Hashtag key={hashtag.get('name')} hashtag={hashtag} />);
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.hashtags'
|
||||
defaultMessage='There are no hashtags results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{this.renderFilterBar()}
|
||||
|
||||
{noResultsMessage || (
|
||||
<ScrollableList
|
||||
key={selectedFilter}
|
||||
scrollKey={`${selectedFilter}:${value}`}
|
||||
isLoading={submitted && !loaded}
|
||||
showLoading={submitted && !loaded && results.isEmpty()}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
placeholderComponent={placeholderComponent}
|
||||
placeholderCount={20}
|
||||
className={classNames({
|
||||
'divide-gray-200 dark:divide-slate-700 divide-solid divide-y': selectedFilter === 'statuses',
|
||||
})}
|
||||
itemClassName={classNames({ 'pb-4': selectedFilter === 'accounts' })}
|
||||
>
|
||||
{searchResults}
|
||||
</ScrollableList>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,172 @@
|
|||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { FormattedMessage, useIntl } from 'react-intl';
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import { expandSearch, setFilter } from 'soapbox/actions/search';
|
||||
import { fetchTrendingStatuses } from 'soapbox/actions/trending_statuses';
|
||||
import Hashtag from 'soapbox/components/hashtag';
|
||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||
import { Tabs } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
import StatusContainer from 'soapbox/containers/status_container';
|
||||
import PlaceholderAccount from 'soapbox/features/placeholder/components/placeholder_account';
|
||||
import PlaceholderHashtag from 'soapbox/features/placeholder/components/placeholder_hashtag';
|
||||
import PlaceholderStatus from 'soapbox/features/placeholder/components/placeholder_status';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import type { SearchFilter } from 'soapbox/reducers/search';
|
||||
|
||||
const messages = defineMessages({
|
||||
accounts: { id: 'search_results.accounts', defaultMessage: 'People' },
|
||||
statuses: { id: 'search_results.statuses', defaultMessage: 'Posts' },
|
||||
hashtags: { id: 'search_results.hashtags', defaultMessage: 'Hashtags' },
|
||||
});
|
||||
|
||||
const SearchResults = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const value = useAppSelector((state) => state.search.submittedValue);
|
||||
const results = useAppSelector((state) => state.search.results);
|
||||
const suggestions = useAppSelector((state) => state.suggestions.items);
|
||||
const trendingStatuses = useAppSelector((state) => state.trending_statuses.items);
|
||||
const trends = useAppSelector((state) => state.trends.items);
|
||||
const submitted = useAppSelector((state) => state.search.submitted);
|
||||
const selectedFilter = useAppSelector((state) => state.search.filter);
|
||||
|
||||
const handleLoadMore = () => dispatch(expandSearch(selectedFilter));
|
||||
|
||||
const selectFilter = (newActiveFilter: SearchFilter) => dispatch(setFilter(newActiveFilter));
|
||||
|
||||
const renderFilterBar = () => {
|
||||
const items = [
|
||||
{
|
||||
text: intl.formatMessage(messages.accounts),
|
||||
action: () => selectFilter('accounts'),
|
||||
name: 'accounts',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.statuses),
|
||||
action: () => selectFilter('statuses'),
|
||||
name: 'statuses',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage(messages.hashtags),
|
||||
action: () => selectFilter('hashtags'),
|
||||
name: 'hashtags',
|
||||
},
|
||||
];
|
||||
|
||||
return <Tabs items={items} activeItem={selectedFilter} />;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchTrendingStatuses());
|
||||
}, []);
|
||||
|
||||
let searchResults;
|
||||
let hasMore = false;
|
||||
let loaded;
|
||||
let noResultsMessage;
|
||||
let placeholderComponent = PlaceholderStatus as React.ComponentType;
|
||||
|
||||
if (selectedFilter === 'accounts') {
|
||||
hasMore = results.accountsHasMore;
|
||||
loaded = results.accountsLoaded;
|
||||
placeholderComponent = PlaceholderAccount;
|
||||
|
||||
if (results.accounts && results.accounts.size > 0) {
|
||||
searchResults = results.accounts.map(accountId => <AccountContainer key={accountId} id={accountId} />);
|
||||
} else if (!submitted && suggestions && !suggestions.isEmpty()) {
|
||||
searchResults = suggestions.map(suggestion => <AccountContainer key={suggestion.account} id={suggestion.account} />);
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.accounts'
|
||||
defaultMessage='There are no people results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFilter === 'statuses') {
|
||||
hasMore = results.statusesHasMore;
|
||||
loaded = results.statusesLoaded;
|
||||
|
||||
if (results.statuses && results.statuses.size > 0) {
|
||||
searchResults = results.statuses.map((statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer key={statusId} id={statusId} />
|
||||
));
|
||||
} else if (!submitted && trendingStatuses && !trendingStatuses.isEmpty()) {
|
||||
searchResults = trendingStatuses.map((statusId: string) => (
|
||||
// @ts-ignore
|
||||
<StatusContainer key={statusId} id={statusId} />
|
||||
));
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.statuses'
|
||||
defaultMessage='There are no posts results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedFilter === 'hashtags') {
|
||||
hasMore = results.hashtagsHasMore;
|
||||
loaded = results.hashtagsLoaded;
|
||||
placeholderComponent = PlaceholderHashtag;
|
||||
|
||||
if (results.hashtags && results.hashtags.size > 0) {
|
||||
searchResults = results.hashtags.map(hashtag => <Hashtag key={hashtag.name} hashtag={hashtag} />);
|
||||
} else if (!submitted && suggestions && !suggestions.isEmpty()) {
|
||||
searchResults = trends.map(hashtag => <Hashtag key={hashtag.name} hashtag={hashtag} />);
|
||||
} else if (loaded) {
|
||||
noResultsMessage = (
|
||||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='empty_column.search.hashtags'
|
||||
defaultMessage='There are no hashtags results for "{term}"'
|
||||
values={{ term: value }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{renderFilterBar()}
|
||||
|
||||
{noResultsMessage || (
|
||||
<ScrollableList
|
||||
key={selectedFilter}
|
||||
scrollKey={`${selectedFilter}:${value}`}
|
||||
isLoading={submitted && !loaded}
|
||||
showLoading={submitted && !loaded && searchResults?.isEmpty()}
|
||||
hasMore={hasMore}
|
||||
onLoadMore={handleLoadMore}
|
||||
placeholderComponent={placeholderComponent}
|
||||
placeholderCount={20}
|
||||
className={classNames({
|
||||
'divide-gray-200 dark:divide-slate-700 divide-solid divide-y': selectedFilter === 'statuses',
|
||||
})}
|
||||
itemClassName={classNames({ 'pb-4': selectedFilter === 'accounts' })}
|
||||
>
|
||||
{searchResults || []}
|
||||
</ScrollableList>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchResults;
|
|
@ -1,33 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
|
||||
import { expandSearch, setFilter } from 'soapbox/actions/search';
|
||||
import { fetchSuggestions, dismissSuggestion } from 'soapbox/actions/suggestions';
|
||||
import { fetchTrendingStatuses } from 'soapbox/actions/trending_statuses';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import SearchResults from '../components/search_results';
|
||||
|
||||
const mapStateToProps = state => {
|
||||
const instance = state.get('instance');
|
||||
|
||||
return {
|
||||
value: state.getIn(['search', 'submittedValue']),
|
||||
results: state.getIn(['search', 'results']),
|
||||
suggestions: state.getIn(['suggestions', 'items']),
|
||||
trendingStatuses: state.getIn(['trending_statuses', 'items']),
|
||||
trends: state.getIn(['trends', 'items']),
|
||||
submitted: state.getIn(['search', 'submitted']),
|
||||
selectedFilter: state.getIn(['search', 'filter']),
|
||||
features: getFeatures(instance),
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions()),
|
||||
fetchTrendingStatuses: () => dispatch(fetchTrendingStatuses()),
|
||||
expandSearch: type => dispatch(expandSearch(type)),
|
||||
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
|
||||
selectFilter: newActiveFilter => dispatch(setFilter(newActiveFilter)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(SearchResults);
|
|
@ -1,4 +1,3 @@
|
|||
import { Set as ImmutableSet } from 'immutable';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { toggleStatusReport } from '../../../actions/reports';
|
||||
|
@ -6,7 +5,7 @@ import StatusCheckBox from '../components/status_check_box';
|
|||
|
||||
const mapStateToProps = (state, { id }) => ({
|
||||
status: state.getIn(['statuses', id]),
|
||||
checked: state.getIn(['reports', 'new', 'status_ids'], ImmutableSet()).includes(id),
|
||||
checked: state.reports.new.status_ids.includes(id),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch, { id }) => ({
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['search', 'value']),
|
||||
submitted: state.getIn(['search', 'submitted']),
|
||||
});
|
||||
|
||||
class Header extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
value: PropTypes.string,
|
||||
submitted: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
submittedValue: '',
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.submitted) {
|
||||
const submittedValue = this.props.value;
|
||||
this.setState({ submittedValue });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { submittedValue } = this.state;
|
||||
|
||||
if (!submittedValue) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='search-header'>
|
||||
<div className='search-header__text-container'>
|
||||
<h1 className='search-header__title-text'>
|
||||
{submittedValue}
|
||||
</h1>
|
||||
</div>
|
||||
<div className='search-header__type-filters'>
|
||||
<div className='account__section-headline'>
|
||||
<div className='search-header__type-filters-tabs'>
|
||||
<NavLink to='/search' activeClassName='active'>
|
||||
<FormattedMessage id='search_results.top' defaultMessage='Top' />
|
||||
</NavLink>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Header);
|
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
import Search from 'soapbox/features/compose/components/search';
|
||||
import SearchResultsContainer from 'soapbox/features/compose/containers/search_results_container';
|
||||
import SearchResults from 'soapbox/features/compose/components/search_results';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.search', defaultMessage: 'Search' },
|
||||
|
@ -16,7 +16,7 @@ const SearchPage = () => {
|
|||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
<div className='space-y-4'>
|
||||
<Search autoFocus autoSubmit />
|
||||
<SearchResultsContainer />
|
||||
<SearchResults />
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
import { List as ImmutableList } from 'immutable';
|
||||
import React from 'react';
|
||||
|
||||
import { render, screen } from '../../../../jest/test-helpers';
|
||||
|
@ -7,16 +7,16 @@ import TrendsPanel from '../trends-panel';
|
|||
describe('<TrendsPanel />', () => {
|
||||
it('renders trending hashtags', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([{
|
||||
trends: {
|
||||
items: ImmutableList([{
|
||||
name: 'hashtag 1',
|
||||
history: [{
|
||||
history: ImmutableList([{
|
||||
day: '1652745600',
|
||||
uses: '294',
|
||||
accounts: '180',
|
||||
}],
|
||||
}]),
|
||||
}]),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
|
@ -27,18 +27,18 @@ describe('<TrendsPanel />', () => {
|
|||
|
||||
it('renders multiple trends', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([
|
||||
trends: {
|
||||
items: ImmutableList([
|
||||
{
|
||||
name: 'hashtag 1',
|
||||
history: [{ accounts: [] }],
|
||||
history: ImmutableList([{ accounts: [] }]),
|
||||
},
|
||||
{
|
||||
name: 'hashtag 2',
|
||||
history: [{ accounts: [] }],
|
||||
history: ImmutableList([{ accounts: [] }]),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={3} />, null, store);
|
||||
|
@ -47,18 +47,18 @@ describe('<TrendsPanel />', () => {
|
|||
|
||||
it('respects the limit prop', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([
|
||||
trends: {
|
||||
items: ImmutableList([
|
||||
{
|
||||
name: 'hashtag 1',
|
||||
history: [{ accounts: [] }],
|
||||
history: ImmutableList([{ accounts: [] }]),
|
||||
},
|
||||
{
|
||||
name: 'hashtag 2',
|
||||
history: [{ accounts: [] }],
|
||||
history: ImmutableList([{ accounts: [] }]),
|
||||
},
|
||||
]),
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
|
@ -67,9 +67,9 @@ describe('<TrendsPanel />', () => {
|
|||
|
||||
it('renders empty', () => {
|
||||
const store = {
|
||||
trends: ImmutableMap({
|
||||
items: fromJS([]),
|
||||
}),
|
||||
trends: {
|
||||
items: ImmutableList([]),
|
||||
},
|
||||
};
|
||||
|
||||
render(<TrendsPanel limit={1} />, null, store);
|
||||
|
|
|
@ -1,101 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { changeAccountNoteComment, submitAccountNote } from 'soapbox/actions/account-notes';
|
||||
import { closeModal } from 'soapbox/actions/modals';
|
||||
import { Modal, Text } from 'soapbox/components/ui';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' },
|
||||
save: { id: 'account_note.save', defaultMessage: 'Save' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
isSubmitting: state.getIn(['account_notes', 'edit', 'isSubmitting']),
|
||||
account: getAccount(state, state.getIn(['account_notes', 'edit', 'account_id'])),
|
||||
comment: state.getIn(['account_notes', 'edit', 'comment']),
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
onConfirm() {
|
||||
dispatch(submitAccountNote());
|
||||
},
|
||||
|
||||
onClose() {
|
||||
dispatch(closeModal());
|
||||
},
|
||||
|
||||
onCommentChange(comment) {
|
||||
dispatch(changeAccountNoteComment(comment));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default @connect(makeMapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class AccountNoteModal extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
isSubmitting: PropTypes.bool,
|
||||
account: PropTypes.object.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
onCommentChange: PropTypes.func.isRequired,
|
||||
comment: PropTypes.string,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
handleCommentChange = e => {
|
||||
this.props.onCommentChange(e.target.value);
|
||||
}
|
||||
|
||||
handleSubmit = () => {
|
||||
this.props.onConfirm();
|
||||
}
|
||||
|
||||
handleKeyDown = e => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
this.handleSubmit();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, isSubmitting, comment, onClose, intl } = this.props;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='account_note.target' defaultMessage='Note for @{target}' values={{ target: account.get('acct') }} />}
|
||||
onClose={onClose}
|
||||
confirmationAction={this.handleSubmit}
|
||||
confirmationText={intl.formatMessage(messages.save)}
|
||||
confirmationDisabled={isSubmitting}
|
||||
>
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage id='account_note.hint' defaultMessage='You can keep notes about this user for yourself (this will not be shared with them):' />
|
||||
</Text>
|
||||
|
||||
<textarea
|
||||
className='setting-text light'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={comment}
|
||||
onChange={this.handleCommentChange}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { changeAccountNoteComment, submitAccountNote } from 'soapbox/actions/account-notes';
|
||||
import { closeModal } from 'soapbox/actions/modals';
|
||||
import { Modal, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
placeholder: { id: 'account_note.placeholder', defaultMessage: 'No comment provided' },
|
||||
save: { id: 'account_note.save', defaultMessage: 'Save' },
|
||||
});
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const AccountNoteModal = () => {
|
||||
const intl = useIntl();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const isSubmitting = useAppSelector((state) => state.account_notes.edit.isSubmitting);
|
||||
const account = useAppSelector((state) => getAccount(state, state.account_notes.edit.account!));
|
||||
const comment = useAppSelector((state) => state.account_notes.edit.comment);
|
||||
|
||||
const onClose = () => {
|
||||
dispatch(closeModal('ACCOUNT_NOTE'));
|
||||
};
|
||||
|
||||
const handleCommentChange: React.ChangeEventHandler<HTMLTextAreaElement> = e => {
|
||||
dispatch(changeAccountNoteComment(e.target.value));
|
||||
};
|
||||
|
||||
const handleSubmit = () => {
|
||||
dispatch(submitAccountNote());
|
||||
};
|
||||
|
||||
const handleKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = e => {
|
||||
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
|
||||
handleSubmit();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={<FormattedMessage id='account_note.target' defaultMessage='Note for @{target}' values={{ target: account!.acct }} />}
|
||||
onClose={onClose}
|
||||
confirmationAction={handleSubmit}
|
||||
confirmationText={intl.formatMessage(messages.save)}
|
||||
confirmationDisabled={isSubmitting}
|
||||
>
|
||||
<Text theme='muted'>
|
||||
<FormattedMessage id='account_note.hint' defaultMessage='You can keep notes about this user for yourself (this will not be shared with them):' />
|
||||
</Text>
|
||||
|
||||
<textarea
|
||||
className='setting-text light'
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
value={comment}
|
||||
onChange={handleCommentChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={isSubmitting}
|
||||
autoFocus
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default AccountNoteModal;
|
|
@ -1,18 +1,27 @@
|
|||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { spring } from 'react-motion';
|
||||
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import StatusContent from 'soapbox/components/status_content';
|
||||
import { Stack } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
|
||||
import Icon from '../../../components/icon';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import { Stack } from '../../../components/ui';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import Motion from '../util/optional_motion';
|
||||
|
||||
const ActionsModal = ({ status, actions, onClick, onClose }) => {
|
||||
const renderAction = (action, i) => {
|
||||
import type { Menu, MenuItem } from 'soapbox/components/dropdown_menu';
|
||||
import type { Status as StatusEntity } from 'soapbox/types/entities';
|
||||
|
||||
interface IActionsModal {
|
||||
status: StatusEntity,
|
||||
actions: Menu,
|
||||
onClick: () => void,
|
||||
onClose: () => void,
|
||||
}
|
||||
|
||||
const ActionsModal: React.FC<IActionsModal> = ({ status, actions, onClick, onClose }) => {
|
||||
const renderAction = (action: MenuItem | null, i: number) => {
|
||||
if (action === null) {
|
||||
return <li key={`sep-${i}`} className='dropdown-menu__separator' />;
|
||||
}
|
||||
|
@ -48,9 +57,10 @@ const ActionsModal = ({ status, actions, onClick, onClose }) => {
|
|||
{status && (
|
||||
<Stack space={2} className='p-4 bg-gray-50 dark:bg-slate-800 border-b border-solid border-gray-200 dark:border-gray-700'>
|
||||
<AccountContainer
|
||||
account={status.get('account')}
|
||||
key={status.account as string}
|
||||
id={status.account as string}
|
||||
showProfileHoverCard={false}
|
||||
timestamp={status.get('created_at')}
|
||||
timestamp={status.created_at}
|
||||
/>
|
||||
<StatusContent status={status} />
|
||||
</Stack>
|
||||
|
@ -73,11 +83,4 @@ const ActionsModal = ({ status, actions, onClick, onClose }) => {
|
|||
);
|
||||
};
|
||||
|
||||
ActionsModal.propTypes = {
|
||||
status: ImmutablePropTypes.record,
|
||||
actions: PropTypes.array,
|
||||
onClick: PropTypes.func,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default ActionsModal;
|
|
@ -1,37 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
import Pullable from 'soapbox/components/pullable';
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
|
||||
import ColumnHeader from './column_header';
|
||||
|
||||
export default class UIColumn extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
heading: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
active: PropTypes.bool,
|
||||
showBackBtn: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
showBackBtn: true,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { heading, icon, children, active, showBackBtn, ...rest } = this.props;
|
||||
const columnHeaderId = heading && heading.replace(/ /g, '-');
|
||||
|
||||
return (
|
||||
<Column aria-labelledby={columnHeaderId} {...rest}>
|
||||
{heading && <ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} showBackBtn={showBackBtn} />}
|
||||
<Pullable>
|
||||
{children}
|
||||
</Pullable>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
import React from 'react';
|
||||
|
||||
import Pullable from 'soapbox/components/pullable';
|
||||
import { Column } from 'soapbox/components/ui';
|
||||
|
||||
import ColumnHeader from './column_header';
|
||||
|
||||
import type { IColumn } from 'soapbox/components/ui/column/column';
|
||||
|
||||
interface IUIColumn extends IColumn {
|
||||
heading?: string,
|
||||
icon?: string,
|
||||
active?: boolean,
|
||||
}
|
||||
|
||||
const UIColumn: React.FC<IUIColumn> = ({
|
||||
heading,
|
||||
icon,
|
||||
children,
|
||||
active,
|
||||
...rest
|
||||
}) => {
|
||||
const columnHeaderId = heading && heading.replace(/ /g, '-');
|
||||
|
||||
return (
|
||||
<Column aria-labelledby={columnHeaderId} {...rest}>
|
||||
{heading && <ColumnHeader icon={icon} active={active} type={heading} columnHeaderId={columnHeaderId} />}
|
||||
<Pullable>
|
||||
{children}
|
||||
</Pullable>
|
||||
</Column>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default UIColumn;
|
|
@ -1,32 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import Column from './column';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column_forbidden.title', defaultMessage: 'Forbidden' },
|
||||
body: { id: 'column_forbidden.body', defaultMessage: 'You do not have permission to access this page.' },
|
||||
});
|
||||
|
||||
class ColumnForbidden extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl: { formatMessage } } = this.props;
|
||||
|
||||
return (
|
||||
<Column label={formatMessage(messages.title)}>
|
||||
<div className='error-column'>
|
||||
{formatMessage(messages.body)}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default injectIntl(ColumnForbidden);
|
|
@ -0,0 +1,23 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import Column from './column';
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'column_forbidden.title', defaultMessage: 'Forbidden' },
|
||||
body: { id: 'column_forbidden.body', defaultMessage: 'You do not have permission to access this page.' },
|
||||
});
|
||||
|
||||
const ColumnForbidden = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
return (
|
||||
<Column label={intl.formatMessage(messages.title)}>
|
||||
<div className='error-column'>
|
||||
{intl.formatMessage(messages.body)}
|
||||
</div>
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
export default ColumnForbidden;
|
|
@ -1,40 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
|
||||
// import classNames from 'classnames';
|
||||
// import Icon from 'soapbox/components/icon';
|
||||
import SubNavigation from 'soapbox/components/sub_navigation';
|
||||
|
||||
export default class ColumnHeader extends React.PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
icon: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
columnHeaderId: PropTypes.string,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
this.props.onClick();
|
||||
}
|
||||
|
||||
render() {
|
||||
const { type } = this.props;
|
||||
return <SubNavigation message={type} />;
|
||||
}
|
||||
|
||||
// render() {
|
||||
// const { icon, type, active, columnHeaderId } = this.props;
|
||||
//
|
||||
// return (
|
||||
// <h1 className={classNames('column-header', { active })} id={columnHeaderId || null}>
|
||||
// <button onClick={this.handleClick}>
|
||||
// {icon && <Icon id={icon} fixedWidth className='column-header__icon' />}
|
||||
// {type}
|
||||
// </button>
|
||||
// </h1>
|
||||
// );
|
||||
// }
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
|
||||
// import classNames from 'classnames';
|
||||
// import Icon from 'soapbox/components/icon';
|
||||
import SubNavigation from 'soapbox/components/sub_navigation';
|
||||
|
||||
interface IColumnHeader {
|
||||
icon?: string,
|
||||
type: string
|
||||
active?: boolean,
|
||||
columnHeaderId?: string,
|
||||
}
|
||||
|
||||
const ColumnHeader: React.FC<IColumnHeader> = ({ type }) => {
|
||||
return <SubNavigation message={type} />;
|
||||
};
|
||||
|
||||
export default ColumnHeader;
|
||||
|
||||
// export default class ColumnHeader extends React.PureComponent {
|
||||
|
||||
// static propTypes = {
|
||||
// icon: PropTypes.string,
|
||||
// type: PropTypes.string,
|
||||
// active: PropTypes.bool,
|
||||
// onClick: PropTypes.func,
|
||||
// columnHeaderId: PropTypes.string,
|
||||
// };
|
||||
|
||||
// handleClick = () => {
|
||||
// this.props.onClick();
|
||||
// }
|
||||
|
||||
// render() {
|
||||
// const { icon, type, active, columnHeaderId } = this.props;
|
||||
|
||||
// return (
|
||||
// <h1 className={classNames('column-header', { active })} id={columnHeaderId || null}>
|
||||
// <button onClick={this.handleClick}>
|
||||
// {icon && <Icon id={icon} fixedWidth className='column-header__icon' />}
|
||||
// {type}
|
||||
// </button>
|
||||
// </h1>
|
||||
// );
|
||||
// }
|
||||
|
||||
// }
|
|
@ -77,14 +77,14 @@ const ReportModal = ({ onClose }: IReportModal) => {
|
|||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const accountId = useAppSelector((state) => state.reports.getIn(['new', 'account_id']) as string);
|
||||
const account = useAccount(accountId);
|
||||
const accountId = useAppSelector((state) => state.reports.new.account_id);
|
||||
const account = useAccount(accountId as string);
|
||||
|
||||
const isBlocked = useAppSelector((state) => state.reports.getIn(['new', 'block']) as boolean);
|
||||
const isSubmitting = useAppSelector((state) => state.reports.getIn(['new', 'isSubmitting']) as boolean);
|
||||
const isBlocked = useAppSelector((state) => state.reports.new.block);
|
||||
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
|
||||
const rules = useAppSelector((state) => state.rules.items);
|
||||
const ruleIds = useAppSelector((state) => state.reports.getIn(['new', 'rule_ids']) as ImmutableSet<string>);
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.getIn(['new', 'status_ids']) as ImmutableSet<string>);
|
||||
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
||||
|
||||
const isReportingAccount = useMemo(() => selectedStatusIds.size === 0, []);
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
|
|
@ -30,11 +30,11 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => {
|
|||
const features = useFeatures();
|
||||
const intl = useIntl();
|
||||
|
||||
const statusIds = useAppSelector((state) => OrderedSet(state.timelines.getIn([`account:${account.id}:with_replies`, 'items'])).union(state.reports.getIn(['new', 'status_ids']) as Iterable<unknown>) as OrderedSet<string>);
|
||||
const isBlocked = useAppSelector((state) => state.reports.getIn(['new', 'block']) as boolean);
|
||||
const isForward = useAppSelector((state) => state.reports.getIn(['new', 'forward']) as boolean);
|
||||
const statusIds = useAppSelector((state) => OrderedSet(state.timelines.getIn([`account:${account.id}:with_replies`, 'items'])).union(state.reports.new.status_ids) as OrderedSet<string>);
|
||||
const isBlocked = useAppSelector((state) => state.reports.new.block);
|
||||
const isForward = useAppSelector((state) => state.reports.new.forward);
|
||||
const canForward = isRemote(account as any) && features.federating;
|
||||
const isSubmitting = useAppSelector((state) => state.reports.getIn(['new', 'isSubmitting']) as boolean);
|
||||
const isSubmitting = useAppSelector((state) => state.reports.new.isSubmitting);
|
||||
|
||||
const [showAdditionalStatuses, setShowAdditionalStatuses] = useState<boolean>(false);
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import { fetchRules } from 'soapbox/actions/rules';
|
|||
import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
||||
import type { Set as ImmutableSet } from 'immutable';
|
||||
import type { ReducerAccount } from 'soapbox/reducers/accounts';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -31,12 +30,12 @@ const ReasonStep = (_props: IReasonStep) => {
|
|||
const [isNearBottom, setNearBottom] = useState<boolean>(false);
|
||||
const [isNearTop, setNearTop] = useState<boolean>(true);
|
||||
|
||||
const comment = useAppSelector((state) => state.reports.getIn(['new', 'comment']) as string);
|
||||
const comment = useAppSelector((state) => state.reports.new.comment);
|
||||
const rules = useAppSelector((state) => state.rules.items);
|
||||
const ruleIds = useAppSelector((state) => state.reports.getIn(['new', 'rule_ids']) as ImmutableSet<string>);
|
||||
const ruleIds = useAppSelector((state) => state.reports.new.rule_ids);
|
||||
const shouldRequireRule = rules.length > 0;
|
||||
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.getIn(['new', 'status_ids']) as ImmutableSet<string>);
|
||||
const selectedStatusIds = useAppSelector((state) => state.reports.new.status_ids);
|
||||
const isReportingAccount = useMemo(() => selectedStatusIds.size === 0, []);
|
||||
|
||||
const handleCommentChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Modal, HStack, Stack, Text } from 'soapbox/components/ui';
|
|||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
isSubmitting: state.getIn(['reports', 'new', 'isSubmitting']),
|
||||
isSubmitting: state.reports.new.isSubmitting,
|
||||
account: state.getIn(['mutes', 'new', 'account']),
|
||||
notifications: state.getIn(['mutes', 'new', 'notifications']),
|
||||
};
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import * as React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
@ -15,10 +14,10 @@ interface ITrendsPanel {
|
|||
const TrendsPanel = ({ limit }: ITrendsPanel) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const trends: any = useAppSelector((state) => state.trends.get('items'));
|
||||
const trends = useAppSelector((state) => state.trends.get('items'));
|
||||
|
||||
const sortedTrends = React.useMemo(() => {
|
||||
return trends.sort((a: ImmutableMap<string, any>, b: ImmutableMap<string, any>) => {
|
||||
return trends.sort((a, b) => {
|
||||
const num_a = Number(a.getIn(['history', 0, 'accounts']));
|
||||
const num_b = Number(b.getIn(['history', 0, 'accounts']));
|
||||
return num_b - num_a;
|
||||
|
@ -35,8 +34,8 @@ const TrendsPanel = ({ limit }: ITrendsPanel) => {
|
|||
|
||||
return (
|
||||
<Widget title={<FormattedMessage id='trends.title' defaultMessage='Trends' />}>
|
||||
{sortedTrends.map((hashtag: ImmutableMap<string, any>) => (
|
||||
<Hashtag key={hashtag.get('name')} hashtag={hashtag} />
|
||||
{sortedTrends.map((hashtag) => (
|
||||
<Hashtag key={hashtag.name} hashtag={hashtag} />
|
||||
))}
|
||||
</Widget>
|
||||
);
|
||||
|
|
|
@ -16,10 +16,10 @@ describe('modal reducer', () => {
|
|||
modalType: 'type1',
|
||||
modalProps: { props1: '1' },
|
||||
};
|
||||
expect(reducer(state, action)).toMatchObject(ImmutableList([{
|
||||
expect(reducer(state, action).toJS()).toMatchObject([{
|
||||
modalType: 'type1',
|
||||
modalProps: { props1: '1' },
|
||||
}]));
|
||||
}]);
|
||||
});
|
||||
|
||||
it('should handle MODAL_CLOSE', () => {
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
|
||||
|
||||
import reducer from '../reports';
|
||||
|
||||
describe('reports reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual(ImmutableMap({
|
||||
new: ImmutableMap({
|
||||
expect(reducer(undefined, {}).toJS()).toEqual({
|
||||
new: {
|
||||
isSubmitting: false,
|
||||
account_id: null,
|
||||
status_ids: ImmutableSet(),
|
||||
status_ids: [],
|
||||
comment: '',
|
||||
forward: false,
|
||||
block: false,
|
||||
rule_ids: ImmutableSet(),
|
||||
}),
|
||||
}));
|
||||
rule_ids: [],
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||
|
||||
import reducer from '../trends';
|
||||
|
||||
describe('trends reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual(ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
expect(reducer(undefined, {}).toJS()).toEqual({
|
||||
items: [],
|
||||
isLoading: false,
|
||||
}));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
const EditRecord = ImmutableRecord({
|
||||
isSubmitting: false,
|
||||
account: null,
|
||||
comment: null,
|
||||
comment: '',
|
||||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
|
@ -26,7 +26,7 @@ export default function account_notes(state: State = ReducerRecord(), action: An
|
|||
case ACCOUNT_NOTE_INIT_MODAL:
|
||||
return state.withMutations((state) => {
|
||||
state.setIn(['edit', 'isSubmitting'], false);
|
||||
state.setIn(['edit', 'account_id'], action.account.get('id'));
|
||||
state.setIn(['edit', 'account'], action.account.get('id'));
|
||||
state.setIn(['edit', 'comment'], action.comment);
|
||||
});
|
||||
case ACCOUNT_NOTE_CHANGE_COMMENT:
|
||||
|
|
|
@ -1,13 +1,22 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modals';
|
||||
|
||||
const initialState = ImmutableList();
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
export default function modal(state = initialState, action) {
|
||||
|
||||
const ModalRecord = ImmutableRecord({
|
||||
modalType: '',
|
||||
modalProps: null as Record<string, any> | null,
|
||||
});
|
||||
|
||||
type Modal = ReturnType<typeof ModalRecord>;
|
||||
type State = ImmutableList<Modal>;
|
||||
|
||||
export default function modal(state: State = ImmutableList<Modal>(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case MODAL_OPEN:
|
||||
return state.push({ modalType: action.modalType, modalProps: action.modalProps });
|
||||
return state.push(ModalRecord({ modalType: action.modalType, modalProps: action.modalProps }));
|
||||
case MODAL_CLOSE:
|
||||
if (state.size === 0) {
|
||||
return state;
|
|
@ -1,4 +1,4 @@
|
|||
import { Map as ImmutableMap, Set as ImmutableSet } from 'immutable';
|
||||
import { Record as ImmutableRecord, Set as ImmutableSet } from 'immutable';
|
||||
|
||||
import {
|
||||
REPORT_INIT,
|
||||
|
@ -13,39 +13,45 @@ import {
|
|||
REPORT_RULE_CHANGE,
|
||||
} from '../actions/reports';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
new: ImmutableMap({
|
||||
isSubmitting: false,
|
||||
account_id: null,
|
||||
status_ids: ImmutableSet(),
|
||||
comment: '',
|
||||
forward: false,
|
||||
block: false,
|
||||
rule_ids: ImmutableSet(),
|
||||
}),
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const NewReportRecord = ImmutableRecord({
|
||||
isSubmitting: false,
|
||||
account_id: null as string | null,
|
||||
status_ids: ImmutableSet<string>(),
|
||||
comment: '',
|
||||
forward: false,
|
||||
block: false,
|
||||
rule_ids: ImmutableSet<string>(),
|
||||
});
|
||||
|
||||
export default function reports(state = initialState, action) {
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
new: NewReportRecord(),
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
|
||||
export default function reports(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case REPORT_INIT:
|
||||
return state.withMutations(map => {
|
||||
map.setIn(['new', 'isSubmitting'], false);
|
||||
map.setIn(['new', 'account_id'], action.account.get('id'));
|
||||
map.setIn(['new', 'account_id'], action.account.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());
|
||||
if (state.new.account_id !== action.account.id) {
|
||||
map.setIn(['new', 'status_ids'], action.status ? ImmutableSet([action.status.reblog?.id || action.status.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'))));
|
||||
map.updateIn(['new', 'status_ids'], set => (set as ImmutableSet<string>).add(action.status.reblog?.id || action.status.id));
|
||||
}
|
||||
});
|
||||
case REPORT_STATUS_TOGGLE:
|
||||
return state.updateIn(['new', 'status_ids'], ImmutableSet(), set => {
|
||||
return state.updateIn(['new', 'status_ids'], set => {
|
||||
if (action.checked) {
|
||||
return set.add(action.statusId);
|
||||
return (set as ImmutableSet<string>).add(action.statusId);
|
||||
}
|
||||
|
||||
return set.remove(action.statusId);
|
||||
return (set as ImmutableSet<string>).remove(action.statusId);
|
||||
});
|
||||
case REPORT_COMMENT_CHANGE:
|
||||
return state.setIn(['new', 'comment'], action.comment);
|
||||
|
@ -54,12 +60,12 @@ export default function reports(state = initialState, action) {
|
|||
case REPORT_BLOCK_CHANGE:
|
||||
return state.setIn(['new', 'block'], action.block);
|
||||
case REPORT_RULE_CHANGE:
|
||||
return state.updateIn(['new', 'rule_ids'], ImmutableSet(), (set) => {
|
||||
if (set.includes(action.rule_id)) {
|
||||
return set.remove(action.rule_id);
|
||||
return state.updateIn(['new', 'rule_ids'], (set) => {
|
||||
if ((set as ImmutableSet<string>).includes(action.rule_id)) {
|
||||
return (set as ImmutableSet<string>).remove(action.rule_id);
|
||||
}
|
||||
|
||||
return set.add(action.rule_id);
|
||||
return (set as ImmutableSet<string>).add(action.rule_id);
|
||||
});
|
||||
case REPORT_SUBMIT_REQUEST:
|
||||
return state.setIn(['new', 'isSubmitting'], true);
|
|
@ -1,4 +1,6 @@
|
|||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
import { OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord, fromJS } from 'immutable';
|
||||
|
||||
import { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
import {
|
||||
COMPOSE_MENTION,
|
||||
|
@ -17,26 +19,50 @@ import {
|
|||
SEARCH_EXPAND_SUCCESS,
|
||||
} from '../actions/search';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const HashtagRecord = ImmutableRecord({
|
||||
name: '',
|
||||
url: '',
|
||||
});
|
||||
|
||||
const ResultsRecord = ImmutableRecord({
|
||||
accounts: ImmutableOrderedSet<string>(),
|
||||
statuses: ImmutableOrderedSet<string>(),
|
||||
hashtags: ImmutableOrderedSet<Hashtag>(), // it's a list of maps
|
||||
accountsHasMore: false,
|
||||
statusesHasMore: false,
|
||||
hashtagsHasMore: false,
|
||||
accountsLoaded: false,
|
||||
statusesLoaded: false,
|
||||
hashtagsLoaded: false,
|
||||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
value: '',
|
||||
submitted: false,
|
||||
submittedValue: '',
|
||||
hidden: false,
|
||||
results: ImmutableMap(),
|
||||
filter: 'accounts',
|
||||
results: ResultsRecord(),
|
||||
filter: 'accounts' as SearchFilter,
|
||||
});
|
||||
|
||||
const toIds = items => {
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
type APIEntities = Array<APIEntity>;
|
||||
export type Hashtag = ReturnType<typeof HashtagRecord>;
|
||||
export type SearchFilter = 'accounts' | 'statuses' | 'hashtags';
|
||||
|
||||
const toIds = (items: APIEntities) => {
|
||||
return ImmutableOrderedSet(items.map(item => item.id));
|
||||
};
|
||||
|
||||
const importResults = (state, results, searchTerm, searchType) => {
|
||||
const importResults = (state: State, results: APIEntity, searchTerm: string, searchType: SearchFilter) => {
|
||||
return state.withMutations(state => {
|
||||
if (state.get('value') === searchTerm && state.get('filter') === searchType) {
|
||||
state.set('results', ImmutableMap({
|
||||
if (state.value === searchTerm && state.filter === searchType) {
|
||||
state.set('results', ResultsRecord({
|
||||
accounts: toIds(results.accounts),
|
||||
statuses: toIds(results.statuses),
|
||||
hashtags: fromJS(results.hashtags), // it's a list of maps
|
||||
hashtags: ImmutableOrderedSet(results.hashtags.map(HashtagRecord)), // it's a list of maps
|
||||
accountsHasMore: results.accounts.length >= 20,
|
||||
statusesHasMore: results.statuses.length >= 20,
|
||||
hashtagsHasMore: results.hashtags.length >= 20,
|
||||
|
@ -50,38 +76,38 @@ const importResults = (state, results, searchTerm, searchType) => {
|
|||
});
|
||||
};
|
||||
|
||||
const paginateResults = (state, searchType, results, searchTerm) => {
|
||||
const paginateResults = (state: State, searchType: SearchFilter, results: APIEntity, searchTerm: string) => {
|
||||
return state.withMutations(state => {
|
||||
if (state.get('value') === searchTerm) {
|
||||
if (state.value === searchTerm) {
|
||||
state.setIn(['results', `${searchType}HasMore`], results[searchType].length >= 20);
|
||||
state.setIn(['results', `${searchType}Loaded`], true);
|
||||
state.updateIn(['results', searchType], items => {
|
||||
const data = results[searchType];
|
||||
// Hashtags are a list of maps. Others are IDs.
|
||||
if (searchType === 'hashtags') {
|
||||
return items.concat(fromJS(data));
|
||||
return (items as ImmutableOrderedSet<string>).concat(fromJS(data));
|
||||
} else {
|
||||
return items.concat(toIds(data));
|
||||
return (items as ImmutableOrderedSet<string>).concat(toIds(data));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSubmitted = (state, value) => {
|
||||
const handleSubmitted = (state: State, value: string) => {
|
||||
return state.withMutations(state => {
|
||||
state.set('results', ImmutableMap());
|
||||
state.set('results', ResultsRecord());
|
||||
state.set('submitted', true);
|
||||
state.set('submittedValue', value);
|
||||
});
|
||||
};
|
||||
|
||||
export default function search(state = initialState, action) {
|
||||
export default function search(state = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case SEARCH_CHANGE:
|
||||
return state.set('value', action.value);
|
||||
case SEARCH_CLEAR:
|
||||
return initialState;
|
||||
return ReducerRecord();
|
||||
case SEARCH_SHOW:
|
||||
return state.set('hidden', false);
|
||||
case COMPOSE_REPLY:
|
|
@ -1,5 +1,4 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, Record as ImmutableRecord, fromJS } from 'immutable';
|
||||
import { AnyAction } from 'redux';
|
||||
|
||||
import {
|
||||
MFA_FETCH_SUCCESS,
|
||||
|
@ -11,6 +10,8 @@ import {
|
|||
REVOKE_TOKEN_SUCCESS,
|
||||
} from '../actions/security';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const TokenRecord = ImmutableRecord({
|
||||
id: 0,
|
||||
app_name: '',
|
|
@ -1,31 +0,0 @@
|
|||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
|
||||
|
||||
import {
|
||||
TRENDING_STATUSES_FETCH_REQUEST,
|
||||
TRENDING_STATUSES_FETCH_SUCCESS,
|
||||
} from 'soapbox/actions/trending_statuses';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableOrderedSet(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
const toIds = items => ImmutableOrderedSet(items.map(item => item.id));
|
||||
|
||||
const importStatuses = (state, statuses) => {
|
||||
return state.withMutations(state => {
|
||||
state.set('items', toIds(statuses));
|
||||
state.set('isLoading', false);
|
||||
});
|
||||
};
|
||||
|
||||
export default function trending_statuses(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case TRENDING_STATUSES_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case TRENDING_STATUSES_FETCH_SUCCESS:
|
||||
return importStatuses(state, action.statuses);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import { OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import {
|
||||
TRENDING_STATUSES_FETCH_REQUEST,
|
||||
TRENDING_STATUSES_FETCH_SUCCESS,
|
||||
} from 'soapbox/actions/trending_statuses';
|
||||
import { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
items: ImmutableOrderedSet<string>(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
type APIEntities = Array<APIEntity>;
|
||||
|
||||
const toIds = (items: APIEntities) => ImmutableOrderedSet(items.map(item => item.id));
|
||||
|
||||
const importStatuses = (state: State, statuses: APIEntities) => {
|
||||
return state.withMutations(state => {
|
||||
state.set('items', toIds(statuses));
|
||||
state.set('isLoading', false);
|
||||
});
|
||||
};
|
||||
|
||||
export default function trending_statuses(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case TRENDING_STATUSES_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case TRENDING_STATUSES_FETCH_SUCCESS:
|
||||
return importStatuses(state, action.statuses);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
|
||||
import {
|
||||
TRENDS_FETCH_REQUEST,
|
||||
TRENDS_FETCH_SUCCESS,
|
||||
TRENDS_FETCH_FAIL,
|
||||
} from '../actions/trends';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
export default function trendsReducer(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case TRENDS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case TRENDS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('items', fromJS(action.tags.map((x => x))));
|
||||
map.set('isLoading', false);
|
||||
});
|
||||
case TRENDS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import { List as ImmutableList, Record as ImmutableRecord } from 'immutable';
|
||||
|
||||
import {
|
||||
TRENDS_FETCH_REQUEST,
|
||||
TRENDS_FETCH_SUCCESS,
|
||||
TRENDS_FETCH_FAIL,
|
||||
} from '../actions/trends';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
import type { APIEntity } from 'soapbox/types/entities';
|
||||
|
||||
const HistoryRecord = ImmutableRecord({
|
||||
accounts: '',
|
||||
day: '',
|
||||
uses: '',
|
||||
});
|
||||
|
||||
const TrendingHashtagRecord = ImmutableRecord({
|
||||
name: '',
|
||||
url: '',
|
||||
history: ImmutableList<History>(),
|
||||
});
|
||||
|
||||
const ReducerRecord = ImmutableRecord({
|
||||
items: ImmutableList<TrendingHashtag>(),
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
type State = ReturnType<typeof ReducerRecord>;
|
||||
type History = ReturnType<typeof HistoryRecord>;
|
||||
export type TrendingHashtag = ReturnType<typeof TrendingHashtagRecord>;
|
||||
|
||||
export default function trendsReducer(state: State = ReducerRecord(), action: AnyAction) {
|
||||
switch (action.type) {
|
||||
case TRENDS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
case TRENDS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('items', ImmutableList(action.tags.map((item: APIEntity) => TrendingHashtagRecord({ ...item, history: ImmutableList(item.history.map(HistoryRecord)) }))));
|
||||
map.set('isLoading', false);
|
||||
});
|
||||
case TRENDS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue