@@ -1,8 +1,6 @@ | |||
import api, { getLinks } from '../api' | |||
import Immutable from 'immutable'; | |||
export const ACCOUNT_SET_SELF = 'ACCOUNT_SET_SELF'; | |||
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | |||
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | |||
export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; | |||
@@ -67,13 +65,6 @@ export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; | |||
export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; | |||
export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; | |||
export function setAccountSelf(account) { | |||
return { | |||
type: ACCOUNT_SET_SELF, | |||
account | |||
}; | |||
}; | |||
export function fetchAccount(id) { | |||
return (dispatch, getState) => { | |||
dispatch(fetchAccountRequest(id)); | |||
@@ -1,8 +0,0 @@ | |||
export const ACCESS_TOKEN_SET = 'ACCESS_TOKEN_SET'; | |||
export function setAccessToken(token) { | |||
return { | |||
type: ACCESS_TOKEN_SET, | |||
token: token | |||
}; | |||
}; |
@@ -0,0 +1,17 @@ | |||
import Immutable from 'immutable'; | |||
export const STORE_HYDRATE = 'STORE_HYDRATE'; | |||
const convertState = rawState => | |||
Immutable.fromJS(rawState, (k, v) => | |||
Immutable.Iterable.isIndexed(v) ? v.toList() : v.toMap().mapKeys(x => | |||
Number.isNaN(x * 1) ? x : x * 1)); | |||
export function hydrateStore(rawState) { | |||
const state = convertState(rawState); | |||
return { | |||
type: STORE_HYDRATE, | |||
state | |||
}; | |||
}; |
@@ -7,8 +7,6 @@ import { | |||
refreshTimeline | |||
} from '../actions/timelines'; | |||
import { updateNotifications } from '../actions/notifications'; | |||
import { setAccessToken } from '../actions/meta'; | |||
import { setAccountSelf } from '../actions/accounts'; | |||
import createBrowserHistory from 'history/lib/createBrowserHistory'; | |||
import { | |||
applyRouterMiddleware, | |||
@@ -44,9 +42,12 @@ import pt from 'react-intl/locale-data/pt'; | |||
import hu from 'react-intl/locale-data/hu'; | |||
import uk from 'react-intl/locale-data/uk'; | |||
import getMessagesForLocale from '../locales'; | |||
import { hydrateStore } from '../actions/store'; | |||
const store = configureStore(); | |||
store.dispatch(hydrateStore(window.INITIAL_STATE)); | |||
const browserHistory = useRouterHistory(createBrowserHistory)({ | |||
basename: '/web' | |||
}); | |||
@@ -56,29 +57,26 @@ addLocaleData([...en, ...de, ...es, ...fr, ...pt, ...hu, ...uk]); | |||
const Mastodon = React.createClass({ | |||
propTypes: { | |||
token: React.PropTypes.string.isRequired, | |||
timelines: React.PropTypes.object, | |||
account: React.PropTypes.string, | |||
locale: React.PropTypes.string.isRequired | |||
}, | |||
componentWillMount() { | |||
const { token, account, locale } = this.props; | |||
store.dispatch(setAccessToken(token)); | |||
store.dispatch(setAccountSelf(JSON.parse(account))); | |||
const { locale } = this.props; | |||
if (typeof App !== 'undefined') { | |||
this.subscription = App.cable.subscriptions.create('TimelineChannel', { | |||
received (data) { | |||
switch(data.type) { | |||
case 'update': | |||
return store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message))); | |||
case 'delete': | |||
return store.dispatch(deleteFromTimelines(data.id)); | |||
case 'notification': | |||
return store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale)); | |||
case 'update': | |||
store.dispatch(updateTimeline(data.timeline, JSON.parse(data.message))); | |||
break; | |||
case 'delete': | |||
store.dispatch(deleteFromTimelines(data.id)); | |||
break; | |||
case 'notification': | |||
store.dispatch(updateNotifications(JSON.parse(data.message), getMessagesForLocale(locale), locale)); | |||
break; | |||
} | |||
} | |||
@@ -1,8 +1,10 @@ | |||
import { connect } from 'react-redux'; | |||
import NavigationBar from '../components/navigation_bar'; | |||
const mapStateToProps = (state, props) => ({ | |||
account: state.getIn(['accounts', state.getIn(['meta', 'me'])]) | |||
}); | |||
const mapStateToProps = (state, props) => { | |||
return { | |||
account: state.getIn(['accounts', state.getIn(['meta', 'me'])]) | |||
}; | |||
}; | |||
export default connect(mapStateToProps)(NavigationBar); |
@@ -1,5 +1,4 @@ | |||
import { | |||
ACCOUNT_SET_SELF, | |||
ACCOUNT_FETCH_SUCCESS, | |||
FOLLOWERS_FETCH_SUCCESS, | |||
FOLLOWERS_EXPAND_SUCCESS, | |||
@@ -33,6 +32,7 @@ import { | |||
NOTIFICATIONS_REFRESH_SUCCESS, | |||
NOTIFICATIONS_EXPAND_SUCCESS | |||
} from '../actions/notifications'; | |||
import { STORE_HYDRATE } from '../actions/store'; | |||
import Immutable from 'immutable'; | |||
const normalizeAccount = (state, account) => state.set(account.id, Immutable.fromJS(account)); | |||
@@ -67,38 +67,39 @@ const initialState = Immutable.Map(); | |||
export default function accounts(state = initialState, action) { | |||
switch(action.type) { | |||
case ACCOUNT_SET_SELF: | |||
case ACCOUNT_FETCH_SUCCESS: | |||
case NOTIFICATIONS_UPDATE: | |||
return normalizeAccount(state, action.account); | |||
case FOLLOWERS_FETCH_SUCCESS: | |||
case FOLLOWERS_EXPAND_SUCCESS: | |||
case FOLLOWING_FETCH_SUCCESS: | |||
case FOLLOWING_EXPAND_SUCCESS: | |||
case REBLOGS_FETCH_SUCCESS: | |||
case FAVOURITES_FETCH_SUCCESS: | |||
case COMPOSE_SUGGESTIONS_READY: | |||
case SEARCH_SUGGESTIONS_READY: | |||
case FOLLOW_REQUESTS_FETCH_SUCCESS: | |||
return normalizeAccounts(state, action.accounts); | |||
case NOTIFICATIONS_REFRESH_SUCCESS: | |||
case NOTIFICATIONS_EXPAND_SUCCESS: | |||
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); | |||
case TIMELINE_REFRESH_SUCCESS: | |||
case TIMELINE_EXPAND_SUCCESS: | |||
case ACCOUNT_TIMELINE_FETCH_SUCCESS: | |||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | |||
case CONTEXT_FETCH_SUCCESS: | |||
return normalizeAccountsFromStatuses(state, action.statuses); | |||
case REBLOG_SUCCESS: | |||
case FAVOURITE_SUCCESS: | |||
case UNREBLOG_SUCCESS: | |||
case UNFAVOURITE_SUCCESS: | |||
return normalizeAccountFromStatus(state, action.response); | |||
case TIMELINE_UPDATE: | |||
case STATUS_FETCH_SUCCESS: | |||
return normalizeAccountFromStatus(state, action.status); | |||
default: | |||
return state; | |||
case STORE_HYDRATE: | |||
return state.merge(action.state.get('accounts')); | |||
case ACCOUNT_FETCH_SUCCESS: | |||
case NOTIFICATIONS_UPDATE: | |||
return normalizeAccount(state, action.account); | |||
case FOLLOWERS_FETCH_SUCCESS: | |||
case FOLLOWERS_EXPAND_SUCCESS: | |||
case FOLLOWING_FETCH_SUCCESS: | |||
case FOLLOWING_EXPAND_SUCCESS: | |||
case REBLOGS_FETCH_SUCCESS: | |||
case FAVOURITES_FETCH_SUCCESS: | |||
case COMPOSE_SUGGESTIONS_READY: | |||
case SEARCH_SUGGESTIONS_READY: | |||
case FOLLOW_REQUESTS_FETCH_SUCCESS: | |||
return normalizeAccounts(state, action.accounts); | |||
case NOTIFICATIONS_REFRESH_SUCCESS: | |||
case NOTIFICATIONS_EXPAND_SUCCESS: | |||
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); | |||
case TIMELINE_REFRESH_SUCCESS: | |||
case TIMELINE_EXPAND_SUCCESS: | |||
case ACCOUNT_TIMELINE_FETCH_SUCCESS: | |||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS: | |||
case CONTEXT_FETCH_SUCCESS: | |||
return normalizeAccountsFromStatuses(state, action.statuses); | |||
case REBLOG_SUCCESS: | |||
case FAVOURITE_SUCCESS: | |||
case UNREBLOG_SUCCESS: | |||
case UNFAVOURITE_SUCCESS: | |||
return normalizeAccountFromStatus(state, action.response); | |||
case TIMELINE_UPDATE: | |||
case STATUS_FETCH_SUCCESS: | |||
return normalizeAccountFromStatus(state, action.status); | |||
default: | |||
return state; | |||
} | |||
}; |
@@ -21,7 +21,7 @@ import { | |||
COMPOSE_LISTABILITY_CHANGE | |||
} from '../actions/compose'; | |||
import { TIMELINE_DELETE } from '../actions/timelines'; | |||
import { ACCOUNT_SET_SELF } from '../actions/accounts'; | |||
import { STORE_HYDRATE } from '../actions/store'; | |||
import Immutable from 'immutable'; | |||
const initialState = Immutable.Map({ | |||
@@ -88,6 +88,8 @@ const insertSuggestion = (state, position, token, completion) => { | |||
export default function compose(state = initialState, action) { | |||
switch(action.type) { | |||
case STORE_HYDRATE: | |||
return state.merge(action.state.get('compose')); | |||
case COMPOSE_MOUNT: | |||
return state.set('mounted', true); | |||
case COMPOSE_UNMOUNT: | |||
@@ -97,7 +99,7 @@ export default function compose(state = initialState, action) { | |||
case COMPOSE_VISIBILITY_CHANGE: | |||
return state.set('private', action.checked); | |||
case COMPOSE_LISTABILITY_CHANGE: | |||
return state.set('unlisted', action.checked); | |||
return state.set('unlisted', action.checked); | |||
case COMPOSE_CHANGE: | |||
return state.set('text', action.text); | |||
case COMPOSE_REPLY: | |||
@@ -143,8 +145,6 @@ export default function compose(state = initialState, action) { | |||
} else { | |||
return state; | |||
} | |||
case ACCOUNT_SET_SELF: | |||
return state.set('me', action.account.id).set('private', action.account.locked); | |||
default: | |||
return state; | |||
} | |||
@@ -1,16 +1,16 @@ | |||
import { ACCESS_TOKEN_SET } from '../actions/meta'; | |||
import { ACCOUNT_SET_SELF } from '../actions/accounts'; | |||
import { STORE_HYDRATE } from '../actions/store'; | |||
import Immutable from 'immutable'; | |||
const initialState = Immutable.Map(); | |||
const initialState = Immutable.Map({ | |||
access_token: null, | |||
me: null | |||
}); | |||
export default function meta(state = initialState, action) { | |||
switch(action.type) { | |||
case ACCESS_TOKEN_SET: | |||
return state.set('access_token', action.token); | |||
case ACCOUNT_SET_SELF: | |||
return state.set('me', action.account.id); | |||
default: | |||
return state; | |||
case STORE_HYDRATE: | |||
return state.merge(action.state.get('meta')); | |||
default: | |||
return state; | |||
} | |||
}; |
@@ -1,11 +1,12 @@ | |||
import { createStore, applyMiddleware, compose } from 'redux'; | |||
import thunk from 'redux-thunk'; | |||
import appReducer from '../reducers'; | |||
import { loadingBarMiddleware } from 'react-redux-loading-bar'; | |||
import errorsMiddleware from '../middleware/errors'; | |||
import thunk from 'redux-thunk'; | |||
import appReducer from '../reducers'; | |||
import { loadingBarMiddleware } from 'react-redux-loading-bar'; | |||
import errorsMiddleware from '../middleware/errors'; | |||
import Immutable from 'immutable'; | |||
export default function configureStore(initialState) { | |||
return createStore(appReducer, initialState, compose(applyMiddleware(thunk, loadingBarMiddleware({ | |||
export default function configureStore() { | |||
return createStore(appReducer, compose(applyMiddleware(thunk, loadingBarMiddleware({ | |||
promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'], | |||
}), errorsMiddleware()), window.devToolsExtension ? window.devToolsExtension() : f => f)); | |||
}; |
@@ -3,8 +3,6 @@ | |||
module HomeHelper | |||
def default_props | |||
{ | |||
token: @token, | |||
account: render(file: 'api/v1/accounts/show', locals: { account: current_user.account }, formats: :json), | |||
locale: I18n.locale, | |||
} | |||
end | |||
@@ -1,4 +1,22 @@ | |||
- content_for :header_tags do | |||
:javascript | |||
window.INITIAL_STATE = { | |||
"meta": { | |||
"access_token": "#{@token}", | |||
"locale": "#{I18n.locale}", | |||
"me": #{current_account.id} | |||
}, | |||
"compose": { | |||
"me": #{current_account.id}, | |||
"private": #{current_account.locked?} | |||
}, | |||
"accounts": { | |||
#{current_account.id}: #{render(file: 'api/v1/accounts/show', locals: { account: current_user.account }, formats: :json)} | |||
} | |||
}; | |||
= javascript_include_tag 'application' | |||
= react_component 'Mastodon', default_props, class: 'app-holder', prerender: false |