diff options
Diffstat (limited to 'app/assets/javascripts/components/actions')
10 files changed, 284 insertions, 75 deletions
diff --git a/app/assets/javascripts/components/actions/accounts.jsx b/app/assets/javascripts/components/actions/accounts.jsx index 8d28b051f..0be05034e 100644 --- a/app/assets/javascripts/components/actions/accounts.jsx +++ b/app/assets/javascripts/components/actions/accounts.jsx @@ -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)); @@ -89,32 +80,39 @@ export function fetchAccount(id) { export function fetchAccountTimeline(id, replace = false) { return (dispatch, getState) => { - dispatch(fetchAccountTimelineRequest(id)); - - const ids = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List()); + const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()); const newestId = ids.size > 0 ? ids.first() : null; let params = ''; + let skipLoading = false; if (newestId !== null && !replace) { - params = `?since_id=${newestId}`; + params = `?since_id=${newestId}`; + skipLoading = true; } + dispatch(fetchAccountTimelineRequest(id, skipLoading)); + api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => { - dispatch(fetchAccountTimelineSuccess(id, response.data, replace)); + dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading)); }).catch(error => { - dispatch(fetchAccountTimelineFail(id, error)); + dispatch(fetchAccountTimelineFail(id, error, skipLoading)); }); }; }; export function expandAccountTimeline(id) { return (dispatch, getState) => { - const lastId = getState().getIn(['timelines', 'accounts_timelines', id], Immutable.List()).last(); + const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last(); dispatch(expandAccountTimelineRequest(id)); - api(getState).get(`/api/v1/accounts/${id}/statuses?max_id=${lastId}`).then(response => { + api(getState).get(`/api/v1/accounts/${id}/statuses`, { + params: { + limit: 10, + max_id: lastId + } + }).then(response => { dispatch(expandAccountTimelineSuccess(id, response.data)); }).catch(error => { dispatch(expandAccountTimelineFail(id, error)); @@ -210,27 +208,30 @@ export function unfollowAccountFail(error) { }; }; -export function fetchAccountTimelineRequest(id) { +export function fetchAccountTimelineRequest(id, skipLoading) { return { type: ACCOUNT_TIMELINE_FETCH_REQUEST, - id + id, + skipLoading }; }; -export function fetchAccountTimelineSuccess(id, statuses, replace) { +export function fetchAccountTimelineSuccess(id, statuses, replace, skipLoading) { return { type: ACCOUNT_TIMELINE_FETCH_SUCCESS, id, statuses, - replace + replace, + skipLoading }; }; -export function fetchAccountTimelineFail(id, error) { +export function fetchAccountTimelineFail(id, error, skipLoading) { return { type: ACCOUNT_TIMELINE_FETCH_FAIL, id, - error + error, + skipLoading }; }; @@ -495,6 +496,10 @@ export function expandFollowingFail(id, error) { export function fetchRelationships(account_ids) { return (dispatch, getState) => { + if (account_ids.length === 0) { + return; + } + dispatch(fetchRelationshipsRequest(account_ids)); api(getState).get(`/api/v1/accounts/relationships?${account_ids.map(id => `id[]=${id}`).join('&')}`).then(response => { @@ -508,21 +513,24 @@ export function fetchRelationships(account_ids) { export function fetchRelationshipsRequest(ids) { return { type: RELATIONSHIPS_FETCH_REQUEST, - ids + ids, + skipLoading: true }; }; export function fetchRelationshipsSuccess(relationships) { return { type: RELATIONSHIPS_FETCH_SUCCESS, - relationships + relationships, + skipLoading: true }; }; export function fetchRelationshipsFail(error) { return { type: RELATIONSHIPS_FETCH_FAIL, - error + error, + skipLoading: true }; }; diff --git a/app/assets/javascripts/components/actions/cards.jsx b/app/assets/javascripts/components/actions/cards.jsx new file mode 100644 index 000000000..503c2bfeb --- /dev/null +++ b/app/assets/javascripts/components/actions/cards.jsx @@ -0,0 +1,47 @@ +import api from '../api'; + +export const STATUS_CARD_FETCH_REQUEST = 'STATUS_CARD_FETCH_REQUEST'; +export const STATUS_CARD_FETCH_SUCCESS = 'STATUS_CARD_FETCH_SUCCESS'; +export const STATUS_CARD_FETCH_FAIL = 'STATUS_CARD_FETCH_FAIL'; + +export function fetchStatusCard(id) { + return (dispatch, getState) => { + dispatch(fetchStatusCardRequest(id)); + + api(getState).get(`/api/v1/statuses/${id}/card`).then(response => { + if (!response.data.url || !response.data.title || !response.data.description) { + return; + } + + dispatch(fetchStatusCardSuccess(id, response.data)); + }).catch(error => { + dispatch(fetchStatusCardFail(id, error)); + }); + }; +}; + +export function fetchStatusCardRequest(id) { + return { + type: STATUS_CARD_FETCH_REQUEST, + id, + skipLoading: true + }; +}; + +export function fetchStatusCardSuccess(id, card) { + return { + type: STATUS_CARD_FETCH_SUCCESS, + id, + card, + skipLoading: true + }; +}; + +export function fetchStatusCardFail(id, error) { + return { + type: STATUS_CARD_FETCH_FAIL, + id, + error, + skipLoading: true + }; +}; diff --git a/app/assets/javascripts/components/actions/compose.jsx b/app/assets/javascripts/components/actions/compose.jsx index 05674ba89..6d0188166 100644 --- a/app/assets/javascripts/components/actions/compose.jsx +++ b/app/assets/javascripts/components/actions/compose.jsx @@ -23,6 +23,8 @@ export const COMPOSE_MOUNT = 'COMPOSE_MOUNT'; export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT'; export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE'; +export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE'; +export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE'; export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE'; export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE'; @@ -68,6 +70,7 @@ export function submitCompose() { in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null), media_ids: getState().getIn(['compose', 'media_attachments']).map(item => item.get('id')), sensitive: getState().getIn(['compose', 'sensitive']), + spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), visibility: getState().getIn(['compose', 'private']) ? 'private' : (getState().getIn(['compose', 'unlisted']) ? 'unlisted' : 'public') }).then(function (response) { dispatch(submitComposeSuccess({ ...response.data })); @@ -218,6 +221,20 @@ export function changeComposeSensitivity(checked) { }; }; +export function changeComposeSpoilerness(checked) { + return { + type: COMPOSE_SPOILERNESS_CHANGE, + checked + }; +}; + +export function changeComposeSpoilerText(text) { + return { + type: COMPOSE_SPOILER_TEXT_CHANGE, + text + }; +}; + export function changeComposeVisibility(checked) { return { type: COMPOSE_VISIBILITY_CHANGE, diff --git a/app/assets/javascripts/components/actions/favourites.jsx b/app/assets/javascripts/components/actions/favourites.jsx new file mode 100644 index 000000000..a25c1ae1c --- /dev/null +++ b/app/assets/javascripts/components/actions/favourites.jsx @@ -0,0 +1,83 @@ +import api, { getLinks } from '../api' + +export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; +export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; +export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; + +export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; +export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; +export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; + +export function fetchFavouritedStatuses() { + return (dispatch, getState) => { + dispatch(fetchFavouritedStatusesRequest()); + + api(getState).get('/api/v1/favourites').then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(fetchFavouritedStatusesFail(error)); + }); + }; +}; + +export function fetchFavouritedStatusesRequest() { + return { + type: FAVOURITED_STATUSES_FETCH_REQUEST + }; +}; + +export function fetchFavouritedStatusesSuccess(statuses, next) { + return { + type: FAVOURITED_STATUSES_FETCH_SUCCESS, + statuses, + next + }; +}; + +export function fetchFavouritedStatusesFail(error) { + return { + type: FAVOURITED_STATUSES_FETCH_FAIL, + error + }; +}; + +export function expandFavouritedStatuses() { + return (dispatch, getState) => { + const url = getState().getIn(['status_lists', 'favourites', 'next'], null); + + if (url === null) { + return; + } + + dispatch(expandFavouritedStatusesRequest()); + + api(getState).get(url).then(response => { + const next = getLinks(response).refs.find(link => link.rel === 'next'); + dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); + }).catch(error => { + dispatch(expandFavouritedStatusesFail(error)); + }); + }; +}; + +export function expandFavouritedStatusesRequest() { + return { + type: FAVOURITED_STATUSES_EXPAND_REQUEST + }; +}; + +export function expandFavouritedStatusesSuccess(statuses, next) { + return { + type: FAVOURITED_STATUSES_EXPAND_SUCCESS, + statuses, + next + }; +}; + +export function expandFavouritedStatusesFail(error) { + return { + type: FAVOURITED_STATUSES_EXPAND_FAIL, + error + }; +}; diff --git a/app/assets/javascripts/components/actions/meta.jsx b/app/assets/javascripts/components/actions/meta.jsx deleted file mode 100644 index d0adbce3f..000000000 --- a/app/assets/javascripts/components/actions/meta.jsx +++ /dev/null @@ -1,8 +0,0 @@ -export const ACCESS_TOKEN_SET = 'ACCESS_TOKEN_SET'; - -export function setAccessToken(token) { - return { - type: ACCESS_TOKEN_SET, - token: token - }; -}; diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx index 8bd835406..1731c1857 100644 --- a/app/assets/javascripts/components/actions/notifications.jsx +++ b/app/assets/javascripts/components/actions/notifications.jsx @@ -14,8 +14,6 @@ export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; -export const NOTIFICATIONS_SETTING_CHANGE = 'NOTIFICATIONS_SETTING_CHANGE'; - const fetchRelatedRelationships = (dispatch, notifications) => { const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); @@ -26,21 +24,25 @@ const fetchRelatedRelationships = (dispatch, notifications) => { export function updateNotifications(notification, intlMessages, intlLocale) { return (dispatch, getState) => { + const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); + const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); + dispatch({ type: NOTIFICATIONS_UPDATE, notification, account: notification.account, - status: notification.status + status: notification.status, + meta: playSound ? { sound: 'boop' } : undefined }); fetchRelatedRelationships(dispatch, [notification]); // Desktop notifications - if (typeof window.Notification !== 'undefined' && getState().getIn(['notifications', 'settings', 'alerts', notification.type], false)) { + if (typeof window.Notification !== 'undefined' && showAlert) { const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); const body = $('<p>').html(notification.status ? notification.status.content : '').text(); - new Notification(title, { body, icon: notification.account.avatar }); + new Notification(title, { body, icon: notification.account.avatar, tag: notification.id }); } }; }; @@ -94,13 +96,17 @@ export function expandNotifications() { return (dispatch, getState) => { const url = getState().getIn(['notifications', 'next'], null); - if (url === null) { + if (url === null || getState().getIn(['notifications', 'isLoading'])) { return; } dispatch(expandNotificationsRequest()); - api(getState).get(url).then(response => { + api(getState).get(url, { + params: { + limit: 5 + } + }).then(response => { const next = getLinks(response).refs.find(link => link.rel === 'next'); dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); @@ -133,11 +139,3 @@ export function expandNotificationsFail(error) { error }; }; - -export function changeNotificationsSetting(key, checked) { - return { - type: NOTIFICATIONS_SETTING_CHANGE, - key, - checked - }; -}; diff --git a/app/assets/javascripts/components/actions/settings.jsx b/app/assets/javascripts/components/actions/settings.jsx new file mode 100644 index 000000000..c754b30ca --- /dev/null +++ b/app/assets/javascripts/components/actions/settings.jsx @@ -0,0 +1,19 @@ +import axios from 'axios'; + +export const SETTING_CHANGE = 'SETTING_CHANGE'; + +export function changeSetting(key, value) { + return { + type: SETTING_CHANGE, + key, + value + }; +}; + +export function saveSettings() { + return (_, getState) => { + axios.put('/api/web/settings', { + data: getState().get('settings').toJS() + }); + }; +}; diff --git a/app/assets/javascripts/components/actions/statuses.jsx b/app/assets/javascripts/components/actions/statuses.jsx index cbee94bca..9ac215727 100644 --- a/app/assets/javascripts/components/actions/statuses.jsx +++ b/app/assets/javascripts/components/actions/statuses.jsx @@ -1,6 +1,7 @@ import api from '../api'; import { deleteFromTimelines } from './timelines'; +import { fetchStatusCard } from './cards'; export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; @@ -14,39 +15,44 @@ export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'; export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; -export function fetchStatusRequest(id) { +export function fetchStatusRequest(id, skipLoading) { return { type: STATUS_FETCH_REQUEST, - id: id + id, + skipLoading }; }; export function fetchStatus(id) { return (dispatch, getState) => { - dispatch(fetchStatusRequest(id)); + const skipLoading = getState().getIn(['statuses', id], null) !== null; + + dispatch(fetchStatusRequest(id, skipLoading)); api(getState).get(`/api/v1/statuses/${id}`).then(response => { - dispatch(fetchStatusSuccess(response.data)); + dispatch(fetchStatusSuccess(response.data, skipLoading)); dispatch(fetchContext(id)); + dispatch(fetchStatusCard(id)); }).catch(error => { - dispatch(fetchStatusFail(id, error)); + dispatch(fetchStatusFail(id, error, skipLoading)); }); }; }; -export function fetchStatusSuccess(status, context) { +export function fetchStatusSuccess(status, skipLoading) { return { type: STATUS_FETCH_SUCCESS, - status: status, - context: context + status, + skipLoading }; }; -export function fetchStatusFail(id, error) { +export function fetchStatusFail(id, error, skipLoading) { return { type: STATUS_FETCH_FAIL, - id: id, - error: error + id, + error, + skipLoading }; }; diff --git a/app/assets/javascripts/components/actions/store.jsx b/app/assets/javascripts/components/actions/store.jsx new file mode 100644 index 000000000..3bba99549 --- /dev/null +++ b/app/assets/javascripts/components/actions/store.jsx @@ -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 + }; +}; diff --git a/app/assets/javascripts/components/actions/timelines.jsx b/app/assets/javascripts/components/actions/timelines.jsx index 0e6f09190..29a060e87 100644 --- a/app/assets/javascripts/components/actions/timelines.jsx +++ b/app/assets/javascripts/components/actions/timelines.jsx @@ -14,11 +14,12 @@ export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; -export function refreshTimelineSuccess(timeline, statuses) { +export function refreshTimelineSuccess(timeline, statuses, skipLoading) { return { type: TIMELINE_REFRESH_SUCCESS, - timeline: timeline, - statuses: statuses + timeline, + statuses, + skipLoading }; }; @@ -39,55 +40,65 @@ export function deleteFromTimelines(id) { return (dispatch, getState) => { const accountId = getState().getIn(['statuses', id, 'account']); const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); + const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); dispatch({ type: TIMELINE_DELETE, id, accountId, - references + references, + reblogOf }); }; }; -export function refreshTimelineRequest(timeline, id) { +export function refreshTimelineRequest(timeline, id, skipLoading) { return { type: TIMELINE_REFRESH_REQUEST, timeline, - id + id, + skipLoading }; }; export function refreshTimeline(timeline, id = null) { return function (dispatch, getState) { - dispatch(refreshTimelineRequest(timeline, id)); + if (getState().getIn(['timelines', timeline, 'isLoading'])) { + return; + } const ids = getState().getIn(['timelines', timeline, 'items'], Immutable.List()); const newestId = ids.size > 0 ? ids.first() : null; - let params = ''; - let path = timeline; + let params = ''; + let path = timeline; + let skipLoading = false; if (newestId !== null && getState().getIn(['timelines', timeline, 'loaded'])) { - params = `?since_id=${newestId}`; + params = `?since_id=${newestId}`; + skipLoading = true; } if (id) { path = `${path}/${id}` } + dispatch(refreshTimelineRequest(timeline, id, skipLoading)); + api(getState).get(`/api/v1/timelines/${path}${params}`).then(function (response) { - dispatch(refreshTimelineSuccess(timeline, response.data)); + dispatch(refreshTimelineSuccess(timeline, response.data, skipLoading)); }).catch(function (error) { - dispatch(refreshTimelineFail(timeline, error)); + dispatch(refreshTimelineFail(timeline, error, skipLoading)); }); }; }; -export function refreshTimelineFail(timeline, error) { +export function refreshTimelineFail(timeline, error, skipLoading) { return { type: TIMELINE_REFRESH_FAIL, timeline, - error + error, + skipLoading }; }; @@ -95,6 +106,12 @@ export function expandTimeline(timeline, id = null) { return (dispatch, getState) => { const lastId = getState().getIn(['timelines', timeline, 'items'], Immutable.List()).last(); + if (!lastId || getState().getIn(['timelines', timeline, 'isLoading'])) { + // If timeline is empty, don't try to load older posts since there are none + // Also if already loading + return; + } + dispatch(expandTimelineRequest(timeline)); let path = timeline; @@ -103,7 +120,12 @@ export function expandTimeline(timeline, id = null) { path = `${path}/${id}` } - api(getState).get(`/api/v1/timelines/${path}?max_id=${lastId}`).then(response => { + api(getState).get(`/api/v1/timelines/${path}`, { + params: { + limit: 10, + max_id: lastId + } + }).then(response => { dispatch(expandTimelineSuccess(timeline, response.data)); }).catch(error => { dispatch(expandTimelineFail(timeline, error)); |