diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx
index eacbeef06..803911c6c 100644
--- a/app/assets/javascripts/components/actions/accounts.jsx
+++ b/app/assets/javascripts/components/actions/accounts.jsx
@@ -32,6 +32,14 @@ export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST'
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
+export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
+export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
+export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
+
+export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST';
+export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS';
+export const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL';
+
export function setAccountSelf(account) {
return {
type: ACCOUNT_SET_SELF,
@@ -289,3 +297,73 @@ export function unblockAccountFail(error) {
error: error
};
};
+
+export function fetchFollowers(id) {
+ return (dispatch, getState) => {
+ dispatch(fetchFollowersRequest(id));
+
+ api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => {
+ dispatch(fetchFollowersSuccess(id, response.data));
+ }).catch(error => {
+ dispatch(fetchFollowersFail(id, error));
+ });
+ };
+};
+
+export function fetchFollowersRequest(id) {
+ return {
+ type: FOLLOWERS_FETCH_REQUEST,
+ id: id
+ };
+};
+
+export function fetchFollowersSuccess(id, accounts) {
+ return {
+ type: FOLLOWERS_FETCH_SUCCESS,
+ id: id,
+ accounts: accounts
+ };
+};
+
+export function fetchFollowersFail(id, error) {
+ return {
+ type: FOLLOWERS_FETCH_FAIL,
+ id: id,
+ error: error
+ };
+};
+
+export function fetchFollowing(id) {
+ return (dispatch, getState) => {
+ dispatch(fetchFollowingRequest(id));
+
+ api(getState).get(`/api/v1/accounts/${id}/following`).then(response => {
+ dispatch(fetchFollowingSuccess(id, response.data));
+ }).catch(error => {
+ dispatch(fetchFollowingFail(id, error));
+ });
+ };
+};
+
+export function fetchFollowingRequest(id) {
+ return {
+ type: FOLLOWING_FETCH_REQUEST,
+ id: id
+ };
+};
+
+export function fetchFollowingSuccess(id, accounts) {
+ return {
+ type: FOLLOWING_FETCH_SUCCESS,
+ id: id,
+ accounts: accounts
+ };
+};
+
+export function fetchFollowingFail(id, error) {
+ return {
+ type: FOLLOWING_FETCH_FAIL,
+ id: id,
+ error: error
+ };
+};
diff --git a/app/assets/javascripts/components/actions/suggestions.jsx b/app/assets/javascripts/components/actions/suggestions.jsx
index c70a4d121..6b3aa69dd 100644
--- a/app/assets/javascripts/components/actions/suggestions.jsx
+++ b/app/assets/javascripts/components/actions/suggestions.jsx
@@ -22,10 +22,10 @@ export function fetchSuggestionsRequest() {
};
};
-export function fetchSuggestionsSuccess(suggestions) {
+export function fetchSuggestionsSuccess(accounts) {
return {
type: SUGGESTIONS_FETCH_SUCCESS,
- suggestions: suggestions
+ accounts: accounts
};
};
diff --git a/app/assets/javascripts/components/containers/mastodon.jsx b/app/assets/javascripts/components/containers/mastodon.jsx
index 8e1becbda..3a04ebb09 100644
--- a/app/assets/javascripts/components/containers/mastodon.jsx
+++ b/app/assets/javascripts/components/containers/mastodon.jsx
@@ -26,6 +26,8 @@ import AccountTimeline from '../features/account_timeline';
import HomeTimeline from '../features/home_timeline';
import MentionsTimeline from '../features/mentions_timeline';
import Compose from '../features/compose';
+import Followers from '../features/followers';
+import Following from '../features/following';
const store = configureStore();
@@ -83,6 +85,8 @@ const Mastodon = React.createClass({
Mastodon is still in development and one of the lacking areas at the moment is user discovery.
You can follow people if you know their username and the domain they are on by entering an e-mail-esque address into the form in the bottom of the sidebar.
If the target user is on the same domain as you, just the username will work. The same rule applies to mentioning people in statuses.
The developer of this project can be followed as Gargron@mastodon.social
diff --git a/app/assets/javascripts/components/reducers/index.jsx b/app/assets/javascripts/components/reducers/index.jsx index e9256b8ec..62d6839d7 100644 --- a/app/assets/javascripts/components/reducers/index.jsx +++ b/app/assets/javascripts/components/reducers/index.jsx @@ -6,6 +6,8 @@ import follow from './follow'; import notifications from './notifications'; import { loadingBarReducer } from 'react-redux-loading-bar'; import modal from './modal'; +import user_lists from './user_lists'; +import suggestions from './suggestions'; export default combineReducers({ timelines, @@ -15,4 +17,6 @@ export default combineReducers({ notifications, loadingBar: loadingBarReducer, modal, + user_lists, + suggestions }); diff --git a/app/assets/javascripts/components/reducers/suggestions.jsx b/app/assets/javascripts/components/reducers/suggestions.jsx new file mode 100644 index 000000000..9d2b7d96a --- /dev/null +++ b/app/assets/javascripts/components/reducers/suggestions.jsx @@ -0,0 +1,13 @@ +import { SUGGESTIONS_FETCH_SUCCESS } from '../actions/suggestions'; +import Immutable from 'immutable'; + +const initialState = Immutable.List(); + +export default function suggestions(state = initialState, action) { + switch(action.type) { + case SUGGESTIONS_FETCH_SUCCESS: + return Immutable.List(action.accounts.map(item => item.id)); + default: + return state; + } +} diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index 331cbf59c..59a1fbaa7 100644 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ b/app/assets/javascripts/components/reducers/timelines.jsx @@ -18,7 +18,9 @@ import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_UNBLOCK_SUCCESS, ACCOUNT_TIMELINE_FETCH_SUCCESS, - ACCOUNT_TIMELINE_EXPAND_SUCCESS + ACCOUNT_TIMELINE_EXPAND_SUCCESS, + FOLLOWERS_FETCH_SUCCESS, + FOLLOWING_FETCH_SUCCESS } from '../actions/accounts'; import { STATUS_FETCH_SUCCESS, @@ -206,12 +208,12 @@ function normalizeContext(state, status, ancestors, descendants) { }); }; -function normalizeSuggestions(state, accounts) { +function normalizeAccounts(state, accounts) { accounts.forEach(account => { state = state.setIn(['accounts', account.get('id')], account); }); - return state.set('suggestions', accounts.map(account => account.get('id'))); + return state; }; export default function timelines(state = initialState, action) { @@ -247,7 +249,9 @@ export default function timelines(state = initialState, action) { case ACCOUNT_TIMELINE_EXPAND_SUCCESS: return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); case SUGGESTIONS_FETCH_SUCCESS: - return normalizeSuggestions(state, Immutable.fromJS(action.suggestions)); + case FOLLOWERS_FETCH_SUCCESS: + case FOLLOWING_FETCH_SUCCESS: + return normalizeAccounts(state, Immutable.fromJS(action.accounts)); default: return state; } diff --git a/app/assets/javascripts/components/reducers/user_lists.jsx b/app/assets/javascripts/components/reducers/user_lists.jsx new file mode 100644 index 000000000..ee4b84296 --- /dev/null +++ b/app/assets/javascripts/components/reducers/user_lists.jsx @@ -0,0 +1,21 @@ +import { + FOLLOWERS_FETCH_SUCCESS, + FOLLOWING_FETCH_SUCCESS +} from '../actions/accounts'; +import Immutable from 'immutable'; + +const initialState = Immutable.Map({ + followers: Immutable.Map(), + following: Immutable.Map() +}); + +export default function userLists(state = initialState, action) { + switch(action.type) { + case FOLLOWERS_FETCH_SUCCESS: + return state.setIn(['followers', action.id], Immutable.List(action.accounts.map(item => item.id))); + case FOLLOWING_FETCH_SUCCESS: + return state.setIn(['following', action.id], Immutable.List(action.accounts.map(item => item.id))); + default: + return state; + } +}; diff --git a/app/assets/javascripts/components/selectors/index.jsx b/app/assets/javascripts/components/selectors/index.jsx index b571e43d5..21ee96906 100644 --- a/app/assets/javascripts/components/selectors/index.jsx +++ b/app/assets/javascripts/components/selectors/index.jsx @@ -7,13 +7,15 @@ const getAccounts = state => state.getIn(['timelines', 'accounts']); const getAccountBase = (state, id) => state.getIn(['timelines', 'accounts', id], null); const getAccountRelationship = (state, id) => state.getIn(['timelines', 'relationships', id]); -export const getAccount = createSelector([getAccountBase, getAccountRelationship], (base, relationship) => { - if (base === null) { - return null; - } +export const makeGetAccount = () => { + return createSelector([getAccountBase, getAccountRelationship], (base, relationship) => { + if (base === null) { + return null; + } - return base.set('relationship', relationship); -}); + return base.set('relationship', relationship); + }); +}; const getStatusBase = (state, id) => state.getIn(['timelines', 'statuses', id], null); @@ -65,7 +67,7 @@ export const getNotifications = createSelector([getNotificationsBase], (base) => return arr; }); -const getSuggestionsBase = (state) => state.getIn(['timelines', 'suggestions']); +const getSuggestionsBase = (state) => state.get('suggestions'); export const getSuggestions = createSelector([getSuggestionsBase, getAccounts], (base, accounts) => { return base.map(accountId => accounts.get(accountId));