- {suggestions.size > 0 ? suggestions.map((suggestion: { account: string }) => (
-
diff --git a/app/soapbox/features/search/index.tsx b/app/soapbox/features/search/index.tsx
index 3fdf625f6..c17691c37 100644
--- a/app/soapbox/features/search/index.tsx
+++ b/app/soapbox/features/search/index.tsx
@@ -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 = () => {
-
+
);
diff --git a/app/soapbox/reducers/__tests__/search-test.js b/app/soapbox/reducers/__tests__/search-test.js
index b1138b663..c3b8d1ea0 100644
--- a/app/soapbox/reducers/__tests__/search-test.js
+++ b/app/soapbox/reducers/__tests__/search-test.js
@@ -6,11 +6,11 @@ import {
SEARCH_EXPAND_SUCCESS,
} from 'soapbox/actions/search';
-import reducer from '../search';
+import reducer, { ReducerRecord } from '../search';
describe('search reducer', () => {
it('should return the initial state', () => {
- expect(reducer(undefined, {})).toEqual(ImmutableMap({
+ expect(reducer(undefined, {})).toEqual(ReducerRecord({
value: '',
submitted: false,
submittedValue: '',
@@ -22,7 +22,7 @@ describe('search reducer', () => {
describe('SEARCH_CHANGE', () => {
it('sets the value', () => {
- const state = ImmutableMap({ value: 'hell' });
+ const state = ReducerRecord({ value: 'hell' });
const action = { type: SEARCH_CHANGE, value: 'hello' };
expect(reducer(state, action).get('value')).toEqual('hello');
});
@@ -30,7 +30,7 @@ describe('search reducer', () => {
describe('SEARCH_CLEAR', () => {
it('resets the state', () => {
- const state = ImmutableMap({
+ const state = ReducerRecord({
value: 'hello world',
submitted: true,
submittedValue: 'hello world',
@@ -41,7 +41,7 @@ describe('search reducer', () => {
const action = { type: SEARCH_CLEAR };
- const expected = ImmutableMap({
+ const expected = ReducerRecord({
value: '',
submitted: false,
submittedValue: '',
@@ -56,7 +56,7 @@ describe('search reducer', () => {
describe(SEARCH_EXPAND_SUCCESS, () => {
it('imports hashtags as maps', () => {
- const state = ImmutableMap({
+ const state = ReducerRecord({
value: 'artist',
submitted: true,
submittedValue: 'artist',
@@ -82,7 +82,7 @@ describe('search reducer', () => {
searchType: 'hashtags',
};
- const expected = ImmutableMap({
+ const expected = ReducerRecord({
value: 'artist',
submitted: true,
submittedValue: 'artist',
diff --git a/app/soapbox/reducers/__tests__/suggestions-test.js b/app/soapbox/reducers/__tests__/suggestions-test.js
index 7da0b7f75..3a11b5b56 100644
--- a/app/soapbox/reducers/__tests__/suggestions-test.js
+++ b/app/soapbox/reducers/__tests__/suggestions-test.js
@@ -1,4 +1,7 @@
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import {
+ Record as ImmutableRecord,
+ fromJS,
+} from 'immutable';
import { SUGGESTIONS_DISMISS } from 'soapbox/actions/suggestions';
@@ -6,10 +9,10 @@ import reducer from '../suggestions';
describe('suggestions reducer', () => {
it('should return the initial state', () => {
- expect(reducer(undefined, {})).toEqual(ImmutableMap({
- items: ImmutableList(),
- isLoading: false,
- }));
+ const result = reducer(undefined, {});
+ expect(ImmutableRecord.isRecord(result)).toBe(true);
+ expect(result.items.isEmpty()).toBe(true);
+ expect(result.isLoading).toBe(false);
});
describe('SUGGESTIONS_DISMISS', () => {
diff --git a/app/soapbox/reducers/__tests__/trends-test.js b/app/soapbox/reducers/__tests__/trends-test.js
index 5ebeabb31..22d7d34e4 100644
--- a/app/soapbox/reducers/__tests__/trends-test.js
+++ b/app/soapbox/reducers/__tests__/trends-test.js
@@ -1,12 +1,12 @@
-import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
+import { Record as ImmutableRecord } from 'immutable';
import reducer from '../trends';
describe('trends reducer', () => {
it('should return the initial state', () => {
- expect(reducer(undefined, {})).toEqual(ImmutableMap({
- items: ImmutableList(),
- isLoading: false,
- }));
+ const result = reducer(undefined, {});
+ expect(ImmutableRecord.isRecord(result)).toBe(true);
+ expect(result.items.isEmpty()).toBe(true);
+ expect(result.isLoading).toBe(false);
});
});
diff --git a/app/soapbox/reducers/search.js b/app/soapbox/reducers/search.ts
similarity index 69%
rename from app/soapbox/reducers/search.js
rename to app/soapbox/reducers/search.ts
index e086ef4a0..64d2a6a8c 100644
--- a/app/soapbox/reducers/search.js
+++ b/app/soapbox/reducers/search.ts
@@ -1,4 +1,10 @@
-import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
+import {
+ Map as ImmutableMap,
+ Record as ImmutableRecord,
+ List as ImmutableList,
+ OrderedSet as ImmutableOrderedSet,
+ fromJS,
+} from 'immutable';
import {
COMPOSE_MENTION,
@@ -17,26 +23,39 @@ import {
SEARCH_EXPAND_SUCCESS,
} from '../actions/search';
-const initialState = ImmutableMap({
+import type { AnyAction } from 'redux';
+
+export const ReducerRecord = ImmutableRecord({
value: '',
submitted: false,
submittedValue: '',
hidden: false,
- results: ImmutableMap(),
+ results: ImmutableMap
(),
filter: 'accounts',
});
-const toIds = items => {
+type State = ReturnType;
+
+type IdEntity = { id: string };
+type SearchType = 'accounts' | 'statuses' | 'hashtags';
+
+type Results = {
+ accounts: IdEntity[],
+ statuses: IdEntity[],
+ hashtags: Record[],
+}
+
+const toIds = (items: IdEntity[]) => {
return ImmutableOrderedSet(items.map(item => item.id));
};
-const importResults = (state, results, searchTerm, searchType) => {
+const importResults = (state: State, results: Results, searchTerm: string, searchType: SearchType): State => {
return state.withMutations(state => {
if (state.get('value') === searchTerm && state.get('filter') === searchType) {
state.set('results', ImmutableMap({
accounts: toIds(results.accounts),
statuses: toIds(results.statuses),
- hashtags: fromJS(results.hashtags), // it's a list of maps
+ hashtags: ImmutableList(results.hashtags.map(ImmutableMap)), // it's a list of maps
accountsHasMore: results.accounts.length >= 20,
statusesHasMore: results.statuses.length >= 20,
hashtagsHasMore: results.hashtags.length >= 20,
@@ -50,17 +69,19 @@ const importResults = (state, results, searchTerm, searchType) => {
});
};
-const paginateResults = (state, searchType, results, searchTerm) => {
+const paginateResults = (state: State, searchType: SearchType, results: Results, searchTerm: string): State => {
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') {
+ // @ts-ignore
return items.concat(fromJS(data));
} else {
+ // @ts-ignore
return items.concat(toIds(data));
}
});
@@ -68,7 +89,7 @@ const paginateResults = (state, searchType, results, searchTerm) => {
});
};
-const handleSubmitted = (state, value) => {
+const handleSubmitted = (state: State, value: string): State => {
return state.withMutations(state => {
state.set('results', ImmutableMap());
state.set('submitted', true);
@@ -76,12 +97,12 @@ const handleSubmitted = (state, 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:
diff --git a/app/soapbox/reducers/suggestions.js b/app/soapbox/reducers/suggestions.ts
similarity index 54%
rename from app/soapbox/reducers/suggestions.js
rename to app/soapbox/reducers/suggestions.ts
index ac00eecf1..bca64c9e2 100644
--- a/app/soapbox/reducers/suggestions.js
+++ b/app/soapbox/reducers/suggestions.ts
@@ -1,4 +1,8 @@
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import {
+ Map as ImmutableMap,
+ List as ImmutableList,
+ Record as ImmutableRecord,
+} from 'immutable';
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'soapbox/actions/accounts';
import { DOMAIN_BLOCK_SUCCESS } from 'soapbox/actions/domain_blocks';
@@ -13,42 +17,64 @@ import {
SUGGESTIONS_V2_FETCH_FAIL,
} from '../actions/suggestions';
-const initialState = ImmutableMap({
- items: ImmutableList(),
+import type { AnyAction } from 'redux';
+
+type SuggestionSource = 'past_interactions' | 'staff' | 'global';
+
+type ReducerSuggestion = {
+ source: SuggestionSource,
+ account: string,
+}
+
+type SuggestionAccount = {
+ id: string,
+}
+
+type Suggestion = {
+ source: SuggestionSource,
+ account: SuggestionAccount,
+}
+
+const ReducerRecord = ImmutableRecord({
+ items: ImmutableList>(),
isLoading: false,
});
-// Convert a v1 account into a v2 suggestion
-const accountToSuggestion = account => {
+type State = ReturnType;
+
+/** Convert a v1 account into a v2 suggestion. */
+const accountToSuggestion = (account: SuggestionAccount): ReducerSuggestion => {
return {
source: 'past_interactions',
account: account.id,
};
};
-const importAccounts = (state, accounts) => {
+/** Import plain accounts into the reducer (legacy). */
+const importAccounts = (state: State, accounts: SuggestionAccount[]): State => {
return state.withMutations(state => {
- state.set('items', fromJS(accounts.map(accountToSuggestion)));
+ state.set('items', ImmutableList(accounts.map(account => ImmutableMap(accountToSuggestion(account)))));
state.set('isLoading', false);
});
};
-const importSuggestions = (state, suggestions) => {
+/** Import full suggestion objects. */
+const importSuggestions = (state: State, suggestions: Suggestion[]): State => {
return state.withMutations(state => {
- state.set('items', fromJS(suggestions.map(x => ({ ...x, account: x.account.id }))));
+ state.set('items', ImmutableList(suggestions.map(x => ImmutableMap({ ...x, account: x.account.id }))));
state.set('isLoading', false);
});
};
-const dismissAccount = (state, accountId) => {
+const dismissAccount = (state: State, accountId: string): State => {
return state.update('items', items => items.filterNot(item => item.get('account') === accountId));
};
-const dismissAccounts = (state, accountIds) => {
+const dismissAccounts = (state: State, accountIds: string[]): State => {
return state.update('items', items => items.filterNot(item => accountIds.includes(item.get('account'))));
};
-export default function suggestionsReducer(state = initialState, action) {
+export default function suggestionsReducer(state = ReducerRecord(), action: AnyAction) {
switch (action.type) {
case SUGGESTIONS_FETCH_REQUEST:
case SUGGESTIONS_V2_FETCH_REQUEST:
diff --git a/app/soapbox/reducers/trending_statuses.js b/app/soapbox/reducers/trending_statuses.js
deleted file mode 100644
index ee4fa234b..000000000
--- a/app/soapbox/reducers/trending_statuses.js
+++ /dev/null
@@ -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;
- }
-}
diff --git a/app/soapbox/reducers/trending_statuses.ts b/app/soapbox/reducers/trending_statuses.ts
new file mode 100644
index 000000000..afaf5d9af
--- /dev/null
+++ b/app/soapbox/reducers/trending_statuses.ts
@@ -0,0 +1,37 @@
+import { Record as ImmutableRecord, OrderedSet as ImmutableOrderedSet } from 'immutable';
+
+import {
+ TRENDING_STATUSES_FETCH_REQUEST,
+ TRENDING_STATUSES_FETCH_SUCCESS,
+} from 'soapbox/actions/trending_statuses';
+
+import type { AnyAction } from 'redux';
+
+const ReducerRecord = ImmutableRecord({
+ items: ImmutableOrderedSet(),
+ isLoading: false,
+});
+
+type State = ReturnType;
+
+type IdEntity = { id: string };
+
+const toIds = (items: IdEntity[]) => ImmutableOrderedSet(items.map(item => item.id));
+
+const importStatuses = (state: State, statuses: IdEntity[]): State => {
+ return state.withMutations(state => {
+ state.set('items', toIds(statuses));
+ state.set('isLoading', false);
+ });
+};
+
+export default function trending_statuses(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;
+ }
+}
diff --git a/app/soapbox/reducers/trends.js b/app/soapbox/reducers/trends.ts
similarity index 53%
rename from app/soapbox/reducers/trends.js
rename to app/soapbox/reducers/trends.ts
index eb7df59bf..cb061c7b7 100644
--- a/app/soapbox/reducers/trends.js
+++ b/app/soapbox/reducers/trends.ts
@@ -1,4 +1,8 @@
-import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
+import {
+ Map as ImmutableMap,
+ Record as ImmutableRecord,
+ List as ImmutableList,
+} from 'immutable';
import {
TRENDS_FETCH_REQUEST,
@@ -6,18 +10,20 @@ import {
TRENDS_FETCH_FAIL,
} from '../actions/trends';
-const initialState = ImmutableMap({
- items: ImmutableList(),
+import type { AnyAction } from 'redux';
+
+const ReducerRecord = ImmutableRecord({
+ items: ImmutableList>(),
isLoading: false,
});
-export default function trendsReducer(state = initialState, action) {
+export default function trendsReducer(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', fromJS(action.tags.map((x => x))));
+ map.set('items', ImmutableList(action.tags.map(ImmutableMap)));
map.set('isLoading', false);
});
case TRENDS_FETCH_FAIL: