@@ -1,4 +1,6 @@ | |||
import api, { getLinks } from '../api'; | |||
import asyncDB from '../db/async'; | |||
import { importAccount, importFetchedAccount, importFetchedAccounts } from './importer'; | |||
export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; | |||
export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; | |||
@@ -64,6 +66,24 @@ 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'; | |||
function getFromDB(dispatch, getState, index, id) { | |||
return new Promise((resolve, reject) => { | |||
const request = index.get(id); | |||
request.onerror = reject; | |||
request.onsuccess = () => { | |||
if (!request.result) { | |||
reject(); | |||
return; | |||
} | |||
dispatch(importAccount(request.result)); | |||
resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved)); | |||
}; | |||
}); | |||
} | |||
export function fetchAccount(id) { | |||
return (dispatch, getState) => { | |||
dispatch(fetchRelationships([id])); | |||
@@ -74,9 +94,16 @@ export function fetchAccount(id) { | |||
dispatch(fetchAccountRequest(id)); | |||
api(getState).get(`/api/v1/accounts/${id}`).then(response => { | |||
dispatch(fetchAccountSuccess(response.data)); | |||
}).catch(error => { | |||
asyncDB.then(db => getFromDB( | |||
dispatch, | |||
getState, | |||
db.transaction('accounts', 'read').objectStore('accounts').index('id'), | |||
id | |||
)).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { | |||
dispatch(importFetchedAccount(response.data)); | |||
})).then(() => { | |||
dispatch(fetchAccountSuccess()); | |||
}, error => { | |||
dispatch(fetchAccountFail(id, error)); | |||
}); | |||
}; | |||
@@ -89,10 +116,9 @@ export function fetchAccountRequest(id) { | |||
}; | |||
}; | |||
export function fetchAccountSuccess(account) { | |||
export function fetchAccountSuccess() { | |||
return { | |||
type: ACCOUNT_FETCH_SUCCESS, | |||
account, | |||
}; | |||
}; | |||
@@ -319,6 +345,7 @@ export function fetchFollowers(id) { | |||
api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => { | |||
@@ -364,6 +391,7 @@ export function expandFollowers(id) { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => { | |||
@@ -403,6 +431,7 @@ export function fetchFollowing(id) { | |||
api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => { | |||
@@ -448,6 +477,7 @@ export function expandFollowing(id) { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => { | |||
@@ -529,6 +559,7 @@ export function fetchFollowRequests() { | |||
api(getState).get('/api/v1/follow_requests').then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); | |||
}).catch(error => dispatch(fetchFollowRequestsFail(error))); | |||
}; | |||
@@ -567,6 +598,7 @@ export function expandFollowRequests() { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); | |||
}).catch(error => dispatch(expandFollowRequestsFail(error))); | |||
}; | |||
@@ -1,5 +1,6 @@ | |||
import api, { getLinks } from '../api'; | |||
import { fetchRelationships } from './accounts'; | |||
import { importFetchedAccounts } from './importer'; | |||
export const BLOCKS_FETCH_REQUEST = 'BLOCKS_FETCH_REQUEST'; | |||
export const BLOCKS_FETCH_SUCCESS = 'BLOCKS_FETCH_SUCCESS'; | |||
@@ -15,6 +16,7 @@ export function fetchBlocks() { | |||
api(getState).get('/api/v1/blocks').then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => dispatch(fetchBlocksFail(error))); | |||
@@ -54,6 +56,7 @@ export function expandBlocks() { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => dispatch(expandBlocksFail(error))); | |||
@@ -4,6 +4,7 @@ import { throttle } from 'lodash'; | |||
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light'; | |||
import { tagHistory } from '../settings'; | |||
import { useEmoji } from './emojis'; | |||
import { importFetchedAccounts } from './importer'; | |||
import { | |||
updateTimeline, | |||
@@ -282,6 +283,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => | |||
limit: 4, | |||
}, | |||
}).then(response => { | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(readyComposeSuggestionsAccounts(token, response.data)); | |||
}); | |||
}, 200, { leading: true, trailing: true }); | |||
@@ -1,4 +1,5 @@ | |||
import api, { getLinks } from '../api'; | |||
import { importFetchedStatuses } from './importer'; | |||
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; | |||
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; | |||
@@ -18,6 +19,7 @@ export function fetchFavouritedStatuses() { | |||
api(getState).get('/api/v1/favourites').then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedStatuses(response.data)); | |||
dispatch(fetchFavouritedStatusesSuccess(response.data, next ? next.uri : null)); | |||
}).catch(error => { | |||
dispatch(fetchFavouritedStatusesFail(error)); | |||
@@ -58,6 +60,7 @@ export function expandFavouritedStatuses() { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedStatuses(response.data)); | |||
dispatch(expandFavouritedStatusesSuccess(response.data, next ? next.uri : null)); | |||
}).catch(error => { | |||
dispatch(expandFavouritedStatusesFail(error)); | |||
@@ -0,0 +1,76 @@ | |||
import { putAccounts, putStatuses } from '../../db/modifier'; | |||
import { normalizeAccount, normalizeStatus } from './normalizer'; | |||
export const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; | |||
export const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT'; | |||
export const STATUS_IMPORT = 'STATUS_IMPORT'; | |||
export const STATUSES_IMPORT = 'STATUSES_IMPORT'; | |||
function pushUnique(array, object) { | |||
if (array.every(element => element.id !== object.id)) { | |||
array.push(object); | |||
} | |||
} | |||
export function importAccount(account) { | |||
return { type: ACCOUNT_IMPORT, account }; | |||
} | |||
export function importAccounts(accounts) { | |||
return { type: ACCOUNTS_IMPORT, accounts }; | |||
} | |||
export function importStatus(status) { | |||
return { type: STATUS_IMPORT, status }; | |||
} | |||
export function importStatuses(statuses) { | |||
return { type: STATUSES_IMPORT, statuses }; | |||
} | |||
export function importFetchedAccount(account) { | |||
return importFetchedAccounts([account]); | |||
} | |||
export function importFetchedAccounts(accounts) { | |||
const normalAccounts = []; | |||
function processAccount(account) { | |||
pushUnique(normalAccounts, normalizeAccount(account)); | |||
if (account.moved) { | |||
processAccount(account); | |||
} | |||
} | |||
accounts.forEach(processAccount); | |||
putAccounts(normalAccounts); | |||
return importAccounts(normalAccounts); | |||
} | |||
export function importFetchedStatus(status) { | |||
return importFetchedStatuses([status]); | |||
} | |||
export function importFetchedStatuses(statuses) { | |||
return (dispatch, getState) => { | |||
const accounts = []; | |||
const normalStatuses = []; | |||
function processStatus(status) { | |||
pushUnique(normalStatuses, normalizeStatus(status, getState().getIn(['statuses', status.id]))); | |||
pushUnique(accounts, status.account); | |||
if (status.reblog && status.reblog.id) { | |||
processStatus(status.reblog); | |||
} | |||
} | |||
statuses.forEach(processStatus); | |||
putStatuses(normalStatuses); | |||
dispatch(importFetchedAccounts(accounts)); | |||
dispatch(importStatuses(normalStatuses)); | |||
}; | |||
} |
@@ -0,0 +1,46 @@ | |||
import escapeTextContentForBrowser from 'escape-html'; | |||
import emojify from '../../features/emoji/emoji'; | |||
const domParser = new DOMParser(); | |||
export function normalizeAccount(account) { | |||
account = { ...account }; | |||
const displayName = account.display_name.length === 0 ? account.username : account.display_name; | |||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); | |||
account.note_emojified = emojify(account.note); | |||
return account; | |||
} | |||
export function normalizeStatus(status, normalOldStatus) { | |||
const normalStatus = { ...status }; | |||
normalStatus.account = status.account.id; | |||
if (status.reblog && status.reblog.id) { | |||
normalStatus.reblog = status.reblog.id; | |||
} | |||
// Only calculate these values when status first encountered | |||
// Otherwise keep the ones already in the reducer | |||
if (normalOldStatus) { | |||
normalStatus.search_index = normalOldStatus.get('search_index'); | |||
normalStatus.contentHtml = normalOldStatus.get('contentHtml'); | |||
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml'); | |||
normalStatus.hidden = normalOldStatus.get('hidden'); | |||
} else { | |||
const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n'); | |||
const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { | |||
obj[`:${emoji.shortcode}:`] = emoji; | |||
return obj; | |||
}, {}); | |||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; | |||
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); | |||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap); | |||
normalStatus.hidden = normalStatus.sensitive; | |||
} | |||
return normalStatus; | |||
} |
@@ -1,4 +1,5 @@ | |||
import api from '../api'; | |||
import { importFetchedAccounts, importFetchedStatus } from './importer'; | |||
export const REBLOG_REQUEST = 'REBLOG_REQUEST'; | |||
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS'; | |||
@@ -39,7 +40,8 @@ export function reblog(status) { | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { | |||
// The reblog API method returns a new status wrapped around the original. In this case we are only | |||
// interested in how the original is modified, hence passing it skipping the wrapper | |||
dispatch(reblogSuccess(status, response.data.reblog)); | |||
dispatch(importFetchedStatus(response.data.reblog)); | |||
dispatch(reblogSuccess(status)); | |||
}).catch(function (error) { | |||
dispatch(reblogFail(status, error)); | |||
}); | |||
@@ -51,7 +53,8 @@ export function unreblog(status) { | |||
dispatch(unreblogRequest(status)); | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then(response => { | |||
dispatch(unreblogSuccess(status, response.data)); | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(unreblogSuccess(status)); | |||
}).catch(error => { | |||
dispatch(unreblogFail(status, error)); | |||
}); | |||
@@ -66,11 +69,10 @@ export function reblogRequest(status) { | |||
}; | |||
}; | |||
export function reblogSuccess(status, response) { | |||
export function reblogSuccess(status) { | |||
return { | |||
type: REBLOG_SUCCESS, | |||
status: status, | |||
response: response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -92,11 +94,10 @@ export function unreblogRequest(status) { | |||
}; | |||
}; | |||
export function unreblogSuccess(status, response) { | |||
export function unreblogSuccess(status) { | |||
return { | |||
type: UNREBLOG_SUCCESS, | |||
status: status, | |||
response: response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -115,7 +116,8 @@ export function favourite(status) { | |||
dispatch(favouriteRequest(status)); | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function (response) { | |||
dispatch(favouriteSuccess(status, response.data)); | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(favouriteSuccess(status)); | |||
}).catch(function (error) { | |||
dispatch(favouriteFail(status, error)); | |||
}); | |||
@@ -127,7 +129,8 @@ export function unfavourite(status) { | |||
dispatch(unfavouriteRequest(status)); | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => { | |||
dispatch(unfavouriteSuccess(status, response.data)); | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(unfavouriteSuccess(status)); | |||
}).catch(error => { | |||
dispatch(unfavouriteFail(status, error)); | |||
}); | |||
@@ -142,11 +145,10 @@ export function favouriteRequest(status) { | |||
}; | |||
}; | |||
export function favouriteSuccess(status, response) { | |||
export function favouriteSuccess(status) { | |||
return { | |||
type: FAVOURITE_SUCCESS, | |||
status: status, | |||
response: response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -168,11 +170,10 @@ export function unfavouriteRequest(status) { | |||
}; | |||
}; | |||
export function unfavouriteSuccess(status, response) { | |||
export function unfavouriteSuccess(status) { | |||
return { | |||
type: UNFAVOURITE_SUCCESS, | |||
status: status, | |||
response: response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -191,6 +192,7 @@ export function fetchReblogs(id) { | |||
dispatch(fetchReblogsRequest(id)); | |||
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then(response => { | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchReblogsSuccess(id, response.data)); | |||
}).catch(error => { | |||
dispatch(fetchReblogsFail(id, error)); | |||
@@ -225,6 +227,7 @@ export function fetchFavourites(id) { | |||
dispatch(fetchFavouritesRequest(id)); | |||
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then(response => { | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchFavouritesSuccess(id, response.data)); | |||
}).catch(error => { | |||
dispatch(fetchFavouritesFail(id, error)); | |||
@@ -259,7 +262,8 @@ export function pin(status) { | |||
dispatch(pinRequest(status)); | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then(response => { | |||
dispatch(pinSuccess(status, response.data)); | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(pinSuccess(status)); | |||
}).catch(error => { | |||
dispatch(pinFail(status, error)); | |||
}); | |||
@@ -274,11 +278,10 @@ export function pinRequest(status) { | |||
}; | |||
}; | |||
export function pinSuccess(status, response) { | |||
export function pinSuccess(status) { | |||
return { | |||
type: PIN_SUCCESS, | |||
status, | |||
response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -297,7 +300,8 @@ export function unpin (status) { | |||
dispatch(unpinRequest(status)); | |||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then(response => { | |||
dispatch(unpinSuccess(status, response.data)); | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(unpinSuccess(status)); | |||
}).catch(error => { | |||
dispatch(unpinFail(status, error)); | |||
}); | |||
@@ -312,11 +316,10 @@ export function unpinRequest(status) { | |||
}; | |||
}; | |||
export function unpinSuccess(status, response) { | |||
export function unpinSuccess(status) { | |||
return { | |||
type: UNPIN_SUCCESS, | |||
status, | |||
response, | |||
skipLoading: true, | |||
}; | |||
}; | |||
@@ -1,4 +1,5 @@ | |||
import api from '../api'; | |||
import { importFetchedAccounts } from './importer'; | |||
export const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; | |||
export const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; | |||
@@ -200,9 +201,10 @@ export const deleteListFail = (id, error) => ({ | |||
export const fetchListAccounts = listId => (dispatch, getState) => { | |||
dispatch(fetchListAccountsRequest(listId)); | |||
api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }) | |||
.then(({ data }) => dispatch(fetchListAccountsSuccess(listId, data))) | |||
.catch(err => dispatch(fetchListAccountsFail(listId, err))); | |||
api(getState).get(`/api/v1/lists/${listId}/accounts`, { params: { limit: 0 } }).then(({ data }) => { | |||
dispatch(importFetchedAccounts(data)); | |||
dispatch(fetchListAccountsSuccess(listId, data)); | |||
}).catch(err => dispatch(fetchListAccountsFail(listId, err))); | |||
}; | |||
export const fetchListAccountsRequest = id => ({ | |||
@@ -231,8 +233,10 @@ export const fetchListSuggestions = q => (dispatch, getState) => { | |||
following: true, | |||
}; | |||
api(getState).get('/api/v1/accounts/search', { params }) | |||
.then(({ data }) => dispatch(fetchListSuggestionsReady(q, data))); | |||
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => { | |||
dispatch(importFetchedAccounts(data)); | |||
dispatch(fetchListSuggestionsReady(q, data)); | |||
}); | |||
}; | |||
export const fetchListSuggestionsReady = (query, accounts) => ({ | |||
@@ -1,5 +1,6 @@ | |||
import api, { getLinks } from '../api'; | |||
import { fetchRelationships } from './accounts'; | |||
import { importFetchedAccounts } from './importer'; | |||
import { openModal } from './modal'; | |||
export const MUTES_FETCH_REQUEST = 'MUTES_FETCH_REQUEST'; | |||
@@ -19,6 +20,7 @@ export function fetchMutes() { | |||
api(getState).get('/api/v1/mutes').then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(fetchMutesSuccess(response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => dispatch(fetchMutesFail(error))); | |||
@@ -58,6 +60,7 @@ export function expandMutes() { | |||
api(getState).get(url).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data)); | |||
dispatch(expandMutesSuccess(response.data, next ? next.uri : null)); | |||
dispatch(fetchRelationships(response.data.map(item => item.id))); | |||
}).catch(error => dispatch(expandMutesFail(error))); | |||
@@ -2,6 +2,12 @@ import api, { getLinks } from '../api'; | |||
import { List as ImmutableList } from 'immutable'; | |||
import IntlMessageFormat from 'intl-messageformat'; | |||
import { fetchRelationships } from './accounts'; | |||
import { | |||
importFetchedAccount, | |||
importFetchedAccounts, | |||
importFetchedStatus, | |||
importFetchedStatuses, | |||
} from './importer'; | |||
import { defineMessages } from 'react-intl'; | |||
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; | |||
@@ -41,11 +47,12 @@ export function updateNotifications(notification, intlMessages, intlLocale) { | |||
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true); | |||
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true); | |||
dispatch(importFetchedAccount(notification.account)); | |||
dispatch(importFetchedStatus(notification.status)); | |||
dispatch({ | |||
type: NOTIFICATIONS_UPDATE, | |||
notification, | |||
account: notification.account, | |||
status: notification.status, | |||
meta: playSound ? { sound: 'boop' } : undefined, | |||
}); | |||
@@ -89,6 +96,9 @@ export function refreshNotifications() { | |||
api(getState).get('/api/v1/notifications', { params }).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data.map(item => item.account))); | |||
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); | |||
dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null)); | |||
fetchRelatedRelationships(dispatch, response.data); | |||
}).catch(error => { | |||
@@ -108,8 +118,6 @@ export function refreshNotificationsSuccess(notifications, skipLoading, next) { | |||
return { | |||
type: NOTIFICATIONS_REFRESH_SUCCESS, | |||
notifications, | |||
accounts: notifications.map(item => item.account), | |||
statuses: notifications.map(item => item.status).filter(status => !!status), | |||
skipLoading, | |||
next, | |||
}; | |||
@@ -141,6 +149,10 @@ export function expandNotifications() { | |||
api(getState).get('/api/v1/notifications', { params }).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedAccounts(response.data.map(item => item.account))); | |||
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); | |||
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null)); | |||
fetchRelatedRelationships(dispatch, response.data); | |||
}).catch(error => { | |||
@@ -159,8 +171,6 @@ export function expandNotificationsSuccess(notifications, next) { | |||
return { | |||
type: NOTIFICATIONS_EXPAND_SUCCESS, | |||
notifications, | |||
accounts: notifications.map(item => item.account), | |||
statuses: notifications.map(item => item.status).filter(status => !!status), | |||
next, | |||
}; | |||
}; | |||
@@ -1,4 +1,5 @@ | |||
import api from '../api'; | |||
import { importFetchedStatuses } from './importer'; | |||
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; | |||
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; | |||
@@ -11,6 +12,7 @@ export function fetchPinnedStatuses() { | |||
dispatch(fetchPinnedStatusesRequest()); | |||
api(getState).get(`/api/v1/accounts/${me}/statuses`, { params: { pinned: true } }).then(response => { | |||
dispatch(importFetchedStatuses(response.data)); | |||
dispatch(fetchPinnedStatusesSuccess(response.data, null)); | |||
}).catch(error => { | |||
dispatch(fetchPinnedStatusesFail(error)); | |||
@@ -1,5 +1,6 @@ | |||
import api from '../api'; | |||
import { fetchRelationships } from './accounts'; | |||
import { importFetchedAccounts, importFetchedStatuses } from './importer'; | |||
export const SEARCH_CHANGE = 'SEARCH_CHANGE'; | |||
export const SEARCH_CLEAR = 'SEARCH_CLEAR'; | |||
@@ -38,6 +39,14 @@ export function submitSearch() { | |||
resolve: true, | |||
}, | |||
}).then(response => { | |||
if (response.data.accounts) { | |||
dispatch(importFetchedAccounts(response.data.accounts)); | |||
} | |||
if (response.data.statuses) { | |||
dispatch(importFetchedStatuses(response.data.statuses)); | |||
} | |||
dispatch(fetchSearchSuccess(response.data)); | |||
dispatch(fetchRelationships(response.data.accounts.map(item => item.id))); | |||
}).catch(error => { | |||
@@ -56,8 +65,6 @@ export function fetchSearchSuccess(results) { | |||
return { | |||
type: SEARCH_FETCH_SUCCESS, | |||
results, | |||
accounts: results.accounts, | |||
statuses: results.statuses, | |||
}; | |||
}; | |||
@@ -1,7 +1,10 @@ | |||
import api from '../api'; | |||
import asyncDB from '../db/async'; | |||
import { evictStatus } from '../db/modifier'; | |||
import { deleteFromTimelines } from './timelines'; | |||
import { fetchStatusCard } from './cards'; | |||
import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'; | |||
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; | |||
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; | |||
@@ -34,6 +37,48 @@ export function fetchStatusRequest(id, skipLoading) { | |||
}; | |||
}; | |||
function getFromDB(dispatch, getState, accountIndex, index, id) { | |||
return new Promise((resolve, reject) => { | |||
const request = index.get(id); | |||
request.onerror = reject; | |||
request.onsuccess = () => { | |||
const promises = []; | |||
if (!request.result) { | |||
reject(); | |||
return; | |||
} | |||
dispatch(importStatus(request.result)); | |||
if (getState().getIn(['accounts', request.result.account], null) === null) { | |||
promises.push(new Promise((accountResolve, accountReject) => { | |||
const accountRequest = accountIndex.get(request.result.account); | |||
accountRequest.onerror = accountReject; | |||
accountRequest.onsuccess = () => { | |||
if (!request.result) { | |||
accountReject(); | |||
return; | |||
} | |||
dispatch(importAccount(accountRequest.result)); | |||
accountResolve(); | |||
}; | |||
})); | |||
} | |||
if (request.result.reblog && getState().getIn(['statuses', request.result.reblog], null) === null) { | |||
promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog)); | |||
} | |||
resolve(Promise.all(promises)); | |||
}; | |||
}); | |||
} | |||
export function fetchStatus(id) { | |||
return (dispatch, getState) => { | |||
const skipLoading = getState().getIn(['statuses', id], null) !== null; | |||
@@ -47,18 +92,26 @@ export function fetchStatus(id) { | |||
dispatch(fetchStatusRequest(id, skipLoading)); | |||
api(getState).get(`/api/v1/statuses/${id}`).then(response => { | |||
dispatch(fetchStatusSuccess(response.data, skipLoading)); | |||
}).catch(error => { | |||
asyncDB.then(db => { | |||
const transaction = db.transaction(['accounts', 'statuses'], 'read'); | |||
const accountIndex = transaction.objectStore('accounts').index('id'); | |||
const index = transaction.objectStore('statuses').index('id'); | |||
return getFromDB(dispatch, getState, accountIndex, index, id); | |||
}).then(() => { | |||
dispatch(fetchStatusSuccess(skipLoading)); | |||
}, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => { | |||
dispatch(importFetchedStatus(response.data)); | |||
dispatch(fetchStatusSuccess(skipLoading)); | |||
})).catch(error => { | |||
dispatch(fetchStatusFail(id, error, skipLoading)); | |||
}); | |||
}; | |||
}; | |||
export function fetchStatusSuccess(status, skipLoading) { | |||
export function fetchStatusSuccess(skipLoading) { | |||
return { | |||
type: STATUS_FETCH_SUCCESS, | |||
status, | |||
skipLoading, | |||
}; | |||
}; | |||
@@ -78,6 +131,7 @@ export function deleteStatus(id) { | |||
dispatch(deleteStatusRequest(id)); | |||
api(getState).delete(`/api/v1/statuses/${id}`).then(() => { | |||
evictStatus(id); | |||
dispatch(deleteStatusSuccess(id)); | |||
dispatch(deleteFromTimelines(id)); | |||
}).catch(error => { | |||
@@ -113,6 +167,7 @@ export function fetchContext(id) { | |||
dispatch(fetchContextRequest(id)); | |||
api(getState).get(`/api/v1/statuses/${id}/context`).then(response => { | |||
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants))); | |||
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants)); | |||
}).catch(error => { | |||
@@ -1,5 +1,6 @@ | |||
import { Iterable, fromJS } from 'immutable'; | |||
import { hydrateCompose } from './compose'; | |||
import { importFetchedAccounts } from './importer'; | |||
export const STORE_HYDRATE = 'STORE_HYDRATE'; | |||
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY'; | |||
@@ -18,5 +19,6 @@ export function hydrateStore(rawState) { | |||
}); | |||
dispatch(hydrateCompose()); | |||
dispatch(importFetchedAccounts(Object.values(rawState.accounts))); | |||
}; | |||
}; |
@@ -1,3 +1,4 @@ | |||
import { importFetchedStatus, importFetchedStatuses } from './importer'; | |||
import api, { getLinks } from '../api'; | |||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; | |||
@@ -44,6 +45,8 @@ export function updateTimeline(timeline, status) { | |||
} | |||
} | |||
dispatch(importFetchedStatus(status)); | |||
dispatch({ | |||
type: TIMELINE_UPDATE, | |||
timeline, | |||
@@ -109,6 +112,7 @@ export function refreshTimeline(timelineId, path, params = {}) { | |||
dispatch(refreshTimelineSuccess(timelineId, [], skipLoading, null, true)); | |||
} else { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedStatuses(response.data)); | |||
dispatch(refreshTimelineSuccess(timelineId, response.data, skipLoading, next ? next.uri : null, false)); | |||
} | |||
}).catch(error => { | |||
@@ -152,6 +156,7 @@ export function expandTimeline(timelineId, path, params = {}) { | |||
api(getState).get(path, { params }).then(response => { | |||
const next = getLinks(response).refs.find(link => link.rel === 'next'); | |||
dispatch(importFetchedStatuses(response.data)); | |||
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null)); | |||
}).catch(error => { | |||
dispatch(expandTimelineFail(timelineId, error)); | |||
@@ -0,0 +1,28 @@ | |||
import { me } from '../initial_state'; | |||
export default new Promise((resolve, reject) => { | |||
// Microsoft Edge 17 does not support getAll according to: | |||
// Catalog of standard and vendor APIs across browsers - Microsoft Edge Development | |||
// https://developer.microsoft.com/en-us/microsoft-edge/platform/catalog/?q=specName%3Aindexeddb | |||
if (!me || !('getAll' in IDBObjectStore.prototype)) { | |||
reject(); | |||
return; | |||
} | |||
const request = indexedDB.open('mastodon:' + me); | |||
request.onerror = reject; | |||
request.onsuccess = ({ target }) => resolve(target.result); | |||
request.onupgradeneeded = ({ target }) => { | |||
const accounts = target.result.createObjectStore('accounts', { autoIncrement: true }); | |||
const statuses = target.result.createObjectStore('statuses', { autoIncrement: true }); | |||
accounts.createIndex('id', 'id', { unique: true }); | |||
accounts.createIndex('moved', 'moved'); | |||
statuses.createIndex('id', 'id', { unique: true }); | |||
statuses.createIndex('account', 'account'); | |||
statuses.createIndex('reblog', 'reblog'); | |||
}; | |||
}); |
@@ -0,0 +1,93 @@ | |||
import asyncDB from './async'; | |||
const limit = 1024; | |||
function put(name, objects, callback) { | |||
asyncDB.then(db => { | |||
const putTransaction = db.transaction(name, 'readwrite'); | |||
const putStore = putTransaction.objectStore(name); | |||
const putIndex = putStore.index('id'); | |||
objects.forEach(object => { | |||
function add() { | |||
putStore.add(object); | |||
} | |||
putIndex.getKey(object.id).onsuccess = retrieval => { | |||
if (retrieval.target.result) { | |||
putStore.delete(retrieval.target.result).onsuccess = add; | |||
} else { | |||
add(); | |||
} | |||
}; | |||
}); | |||
putTransaction.oncomplete = () => { | |||
const readTransaction = db.transaction(name, 'readonly'); | |||
const readStore = readTransaction.objectStore(name); | |||
readStore.count().onsuccess = count => { | |||
const excess = count.target.result - limit; | |||
if (excess > 0) { | |||
readStore.getAll(null, excess).onsuccess = | |||
retrieval => callback(retrieval.target.result.map(({ id }) => id)); | |||
} | |||
}; | |||
}; | |||
}); | |||
} | |||
export function evictAccounts(ids) { | |||
asyncDB.then(db => { | |||
const transaction = db.transaction(['accounts', 'statuses'], 'readwrite'); | |||
const accounts = transaction.objectStore('accounts'); | |||
const accountsIdIndex = accounts.index('id'); | |||
const accountsMovedIndex = accounts.index('moved'); | |||
const statuses = transaction.objectStore('statuses'); | |||
const statusesIndex = statuses.index('account'); | |||
function evict(toEvict) { | |||
toEvict.forEach(id => { | |||
accountsMovedIndex.getAllKeys(id).onsuccess = | |||
({ target }) => evict(target.result); | |||
statusesIndex.getAll(id).onsuccess = | |||
({ target }) => evictStatuses(target.result.map(({ id }) => id)); | |||
accountsIdIndex.getKey(id).onsuccess = | |||
({ target }) => target.result && accounts.delete(target.result); | |||
}); | |||
} | |||
evict(ids); | |||
}); | |||
} | |||
export function evictStatus(id) { | |||
return evictStatuses([id]); | |||
} | |||
export function evictStatuses(ids) { | |||
asyncDB.then(db => { | |||
const store = db.transaction('statuses', 'readwrite').objectStore('statuses'); | |||
const idIndex = store.index('id'); | |||
const reblogIndex = store.index('reblog'); | |||
ids.forEach(id => { | |||
reblogIndex.getAllKeys(id).onsuccess = | |||
({ target }) => target.result.forEach(reblogKey => store.delete(reblogKey)); | |||
idIndex.getKey(id).onsuccess = | |||
({ target }) => target.result && store.delete(target.result); | |||
}); | |||
}); | |||
} | |||
export function putAccounts(records) { | |||
put('accounts', records, evictAccounts); | |||
} | |||
export function putStatuses(records) { | |||
put('statuses', records, evictStatuses); | |||
} |
@@ -1,56 +1,7 @@ | |||
import { | |||
ACCOUNT_FETCH_SUCCESS, | |||
FOLLOWERS_FETCH_SUCCESS, | |||
FOLLOWERS_EXPAND_SUCCESS, | |||
FOLLOWING_FETCH_SUCCESS, | |||
FOLLOWING_EXPAND_SUCCESS, | |||
FOLLOW_REQUESTS_FETCH_SUCCESS, | |||
FOLLOW_REQUESTS_EXPAND_SUCCESS, | |||
} from '../actions/accounts'; | |||
import { | |||
BLOCKS_FETCH_SUCCESS, | |||
BLOCKS_EXPAND_SUCCESS, | |||
} from '../actions/blocks'; | |||
import { | |||
MUTES_FETCH_SUCCESS, | |||
MUTES_EXPAND_SUCCESS, | |||
} from '../actions/mutes'; | |||
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; | |||
import { | |||
REBLOG_SUCCESS, | |||
UNREBLOG_SUCCESS, | |||
FAVOURITE_SUCCESS, | |||
UNFAVOURITE_SUCCESS, | |||
REBLOGS_FETCH_SUCCESS, | |||
FAVOURITES_FETCH_SUCCESS, | |||
} from '../actions/interactions'; | |||
import { | |||
TIMELINE_REFRESH_SUCCESS, | |||
TIMELINE_UPDATE, | |||
TIMELINE_EXPAND_SUCCESS, | |||
} from '../actions/timelines'; | |||
import { | |||
STATUS_FETCH_SUCCESS, | |||
CONTEXT_FETCH_SUCCESS, | |||
} from '../actions/statuses'; | |||
import { SEARCH_FETCH_SUCCESS } from '../actions/search'; | |||
import { | |||
NOTIFICATIONS_UPDATE, | |||
NOTIFICATIONS_REFRESH_SUCCESS, | |||
NOTIFICATIONS_EXPAND_SUCCESS, | |||
} from '../actions/notifications'; | |||
import { | |||
FAVOURITED_STATUSES_FETCH_SUCCESS, | |||
FAVOURITED_STATUSES_EXPAND_SUCCESS, | |||
} from '../actions/favourites'; | |||
import { | |||
LIST_ACCOUNTS_FETCH_SUCCESS, | |||
LIST_EDITOR_SUGGESTIONS_READY, | |||
} from '../actions/lists'; | |||
import { STORE_HYDRATE } from '../actions/store'; | |||
import emojify from '../features/emoji/emoji'; | |||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; | |||
import { Map as ImmutableMap, fromJS } from 'immutable'; | |||
import escapeTextContentForBrowser from 'escape-html'; | |||
const initialState = ImmutableMap(); | |||
const normalizeAccount = (state, account) => { | |||
account = { ...account }; | |||
@@ -59,15 +10,6 @@ const normalizeAccount = (state, account) => { | |||
delete account.following_count; | |||
delete account.statuses_count; | |||
const displayName = account.display_name.length === 0 ? account.username : account.display_name; | |||
account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); | |||
account.note_emojified = emojify(account.note); | |||
if (account.moved) { | |||
state = normalizeAccount(state, account.moved); | |||
account.moved = account.moved.id; | |||
} | |||
return state.set(account.id, fromJS(account)); | |||
}; | |||
@@ -79,67 +21,12 @@ const normalizeAccounts = (state, accounts) => { | |||
return state; | |||
}; | |||
const normalizeAccountFromStatus = (state, status) => { | |||
state = normalizeAccount(state, status.account); | |||
if (status.reblog && status.reblog.account) { | |||
state = normalizeAccount(state, status.reblog.account); | |||
} | |||
return state; | |||
}; | |||
const normalizeAccountsFromStatuses = (state, statuses) => { | |||
statuses.forEach(status => { | |||
state = normalizeAccountFromStatus(state, status); | |||
}); | |||
return state; | |||
}; | |||
const initialState = ImmutableMap(); | |||
export default function accounts(state = initialState, action) { | |||
switch(action.type) { | |||
case STORE_HYDRATE: | |||
return normalizeAccounts(state, Object.values(action.state.get('accounts').toJS())); | |||
case ACCOUNT_FETCH_SUCCESS: | |||
case NOTIFICATIONS_UPDATE: | |||
case ACCOUNT_IMPORT: | |||
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 FOLLOW_REQUESTS_FETCH_SUCCESS: | |||
case FOLLOW_REQUESTS_EXPAND_SUCCESS: | |||
case BLOCKS_FETCH_SUCCESS: | |||
case BLOCKS_EXPAND_SUCCESS: | |||
case MUTES_FETCH_SUCCESS: | |||
case MUTES_EXPAND_SUCCESS: | |||
case LIST_ACCOUNTS_FETCH_SUCCESS: | |||
case LIST_EDITOR_SUGGESTIONS_READY: | |||
return action.accounts ? normalizeAccounts(state, action.accounts) : state; | |||
case NOTIFICATIONS_REFRESH_SUCCESS: | |||
case NOTIFICATIONS_EXPAND_SUCCESS: | |||
case SEARCH_FETCH_SUCCESS: | |||
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); | |||
case TIMELINE_REFRESH_SUCCESS: | |||
case TIMELINE_EXPAND_SUCCESS: | |||
case CONTEXT_FETCH_SUCCESS: | |||
case FAVOURITED_STATUSES_FETCH_SUCCESS: | |||
case FAVOURITED_STATUSES_EXPAND_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); | |||
case ACCOUNTS_IMPORT: | |||
return normalizeAccounts(state, action.accounts); | |||
default: | |||
return state; | |||
} | |||
@@ -1,55 +1,8 @@ | |||
import { | |||
ACCOUNT_FETCH_SUCCESS, | |||
FOLLOWERS_FETCH_SUCCESS, | |||
FOLLOWERS_EXPAND_SUCCESS, | |||
FOLLOWING_FETCH_SUCCESS, | |||
FOLLOWING_EXPAND_SUCCESS, | |||
FOLLOW_REQUESTS_FETCH_SUCCESS, | |||
FOLLOW_REQUESTS_EXPAND_SUCCESS, | |||
ACCOUNT_FOLLOW_SUCCESS, | |||
ACCOUNT_UNFOLLOW_SUCCESS, | |||
} from '../actions/accounts'; | |||
import { | |||
BLOCKS_FETCH_SUCCESS, | |||
BLOCKS_EXPAND_SUCCESS, | |||
} from '../actions/blocks'; | |||
import { | |||
MUTES_FETCH_SUCCESS, | |||
MUTES_EXPAND_SUCCESS, | |||
} from '../actions/mutes'; | |||
import { COMPOSE_SUGGESTIONS_READY } from '../actions/compose'; | |||
import { | |||
REBLOG_SUCCESS, | |||
UNREBLOG_SUCCESS, | |||
FAVOURITE_SUCCESS, | |||
UNFAVOURITE_SUCCESS, | |||
REBLOGS_FETCH_SUCCESS, | |||
FAVOURITES_FETCH_SUCCESS, | |||
} from '../actions/interactions'; | |||
import { | |||
TIMELINE_REFRESH_SUCCESS, | |||
TIMELINE_UPDATE, | |||
TIMELINE_EXPAND_SUCCESS, | |||
} from '../actions/timelines'; | |||
import { | |||
STATUS_FETCH_SUCCESS, | |||
CONTEXT_FETCH_SUCCESS, | |||
} from '../actions/statuses'; | |||
import { SEARCH_FETCH_SUCCESS } from '../actions/search'; | |||
import { | |||
NOTIFICATIONS_UPDATE, | |||
NOTIFICATIONS_REFRESH_SUCCESS, | |||
NOTIFICATIONS_EXPAND_SUCCESS, | |||
} from '../actions/notifications'; | |||
import { | |||
FAVOURITED_STATUSES_FETCH_SUCCESS, | |||
FAVOURITED_STATUSES_EXPAND_SUCCESS, | |||
} from '../actions/favourites'; | |||
import { | |||
LIST_ACCOUNTS_FETCH_SUCCESS, | |||
LIST_EDITOR_SUGGESTIONS_READY, | |||
} from '../actions/lists'; | |||
import { STORE_HYDRATE } from '../actions/store'; | |||
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer'; | |||
import { Map as ImmutableMap, fromJS } from 'immutable'; | |||
const normalizeAccount = (state, account) => state.set(account.id, fromJS({ | |||
@@ -66,71 +19,14 @@ const normalizeAccounts = (state, accounts) => { | |||
return state; | |||
}; | |||
const normalizeAccountFromStatus = (state, status) => { | |||
state = normalizeAccount(state, status.account); | |||
if (status.reblog && status.reblog.account) { | |||
state = normalizeAccount(state, status.reblog.account); | |||
} | |||
return state; | |||
}; | |||
const normalizeAccountsFromStatuses = (state, statuses) => { | |||
statuses.forEach(status => { | |||
state = normalizeAccountFromStatus(state, status); | |||
}); | |||
return state; | |||
}; | |||
const initialState = ImmutableMap(); | |||
export default function accountsCounters(state = initialState, action) { | |||
switch(action.type) { | |||
case STORE_HYDRATE: | |||
return state.merge(action.state.get('accounts').map(item => fromJS({ | |||
followers_count: item.get('followers_count'), | |||
following_count: item.get('following_count'), | |||
statuses_count: item.get('statuses_count'), | |||
}))); | |||
case ACCOUNT_FETCH_SUCCESS: | |||
case NOTIFICATIONS_UPDATE: | |||
case ACCOUNT_IMPORT: | |||
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 FOLLOW_REQUESTS_FETCH_SUCCESS: | |||
case FOLLOW_REQUESTS_EXPAND_SUCCESS: | |||
case BLOCKS_FETCH_SUCCESS: | |||
case BLOCKS_EXPAND_SUCCESS: | |||
case MUTES_FETCH_SUCCESS: | |||
case MUTES_EXPAND_SUCCESS: | |||
case LIST_ACCOUNTS_FETCH_SUCCESS: | |||
case LIST_EDITOR_SUGGESTIONS_READY: | |||
return action.accounts ? normalizeAccounts(state, action.accounts) : state; | |||
case NOTIFICATIONS_REFRESH_SUCCESS: | |||
case NOTIFICATIONS_EXPAND_SUCCESS: | |||
case SEARCH_FETCH_SUCCESS: | |||
return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); | |||
case TIMELINE_REFRESH_SUCCESS: | |||
case TIMELINE_EXPAND_SUCCESS: | |||
case CONTEXT_FETCH_SUCCESS: | |||
case FAVOURITED_STATUSES_FETCH_SUCCESS: | |||
case FAVOURITED_STATUSES_EXPAND_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); | |||
case ACCOUNTS_IMPORT: | |||
return normalizeAccounts(state, action.accounts); | |||
case ACCOUNT_FOLLOW_SUCCESS: | |||
return action.alreadyFollowing ? state : | |||
state.updateIn([action.relationship.id, 'followers_count'], num => num + 1); | |||
@@ -1,87 +1,25 @@ | |||
import { | |||
REBLOG_REQUEST, | |||
REBLOG_SUCCESS, | |||
REBLOG_FAIL, | |||
UNREBLOG_SUCCESS, | |||
FAVOURITE_REQUEST, | |||
FAVOURITE_SUCCESS, | |||
FAVOURITE_FAIL, | |||
UNFAVOURITE_SUCCESS, | |||
PIN_SUCCESS, | |||
UNPIN_SUCCESS, | |||
} from '../actions/interactions'; | |||
import { | |||
STATUS_FETCH_SUCCESS, | |||
CONTEXT_FETCH_SUCCESS, | |||
STATUS_MUTE_SUCCESS, | |||
STATUS_UNMUTE_SUCCESS, | |||
STATUS_REVEAL, | |||
STATUS_HIDE, | |||
} from '../actions/statuses'; | |||
import { | |||
TIMELINE_REFRESH_SUCCESS, | |||
TIMELINE_UPDATE, | |||
TIMELINE_DELETE, | |||
TIMELINE_EXPAND_SUCCESS, | |||
} from '../actions/timelines'; | |||
import { | |||
NOTIFICATIONS_UPDATE, | |||
NOTIFICATIONS_REFRESH_SUCCESS, | |||
NOTIFICATIONS_EXPAND_SUCCESS, | |||
} from '../actions/notifications'; | |||
import { | |||
FAVOURITED_STATUSES_FETCH_SUCCESS, | |||
FAVOURITED_STATUSES_EXPAND_SUCCESS, | |||
} from '../actions/favourites'; | |||
import { | |||
PINNED_STATUSES_FETCH_SUCCESS, | |||
} from '../actions/pin_statuses'; | |||
import { SEARCH_FETCH_SUCCESS } from '../actions/search'; | |||
import emojify from '../features/emoji/emoji'; | |||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer'; | |||
import { Map as ImmutableMap, fromJS } from 'immutable'; | |||
import escapeTextContentForBrowser from 'escape-html'; | |||
const domParser = new DOMParser(); | |||
const normalizeStatus = (state, status) => { | |||
if (!status) { | |||
return state; | |||
} | |||
const normalStatus = { ...status }; | |||
normalStatus.account = status.account.id; | |||
if (status.reblog && status.reblog.id) { | |||
state = normalizeStatus(state, status.reblog); | |||
normalStatus.reblog = status.reblog.id; | |||
} | |||
// Only calculate these values when status first encountered | |||
// Otherwise keep the ones already in the reducer | |||
if (!state.has(status.id)) { | |||
const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n'); | |||
const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { | |||
obj[`:${emoji.shortcode}:`] = emoji; | |||
return obj; | |||
}, {}); | |||
const importStatus = (state, status) => state.set(status.id, fromJS(status)); | |||
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent; | |||
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap); | |||
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(normalStatus.spoiler_text || ''), emojiMap); | |||
normalStatus.hidden = normalStatus.sensitive; | |||
} | |||
return state.update(status.id, ImmutableMap(), map => map.mergeDeep(fromJS(normalStatus))); | |||
}; | |||
const normalizeStatuses = (state, statuses) => { | |||
statuses.forEach(status => { | |||
state = normalizeStatus(state, status); | |||
}); | |||
return state; | |||
}; | |||
const importStatuses = (state, statuses) => | |||
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status))); | |||
const deleteStatus = (state, id, references) => { | |||
references.forEach(ref => { | |||
@@ -95,17 +33,10 @@ const initialState = ImmutableMap(); | |||
export default function statuses(state = initialState, action) { | |||
switch(action.type) { | |||
case TIMELINE_UPDATE: | |||
case STATUS_FETCH_SUCCESS: | |||
case NOTIFICATIONS_UPDATE: | |||
return normalizeStatus(state, action.status); | |||
case REBLOG_SUCCESS: | |||
case UNREBLOG_SUCCESS: | |||
case FAVOURITE_SUCCESS: | |||
case UNFAVOURITE_SUCCESS: | |||
case PIN_SUCCESS: | |||
case UNPIN_SUCCESS: | |||
return normalizeStatus(state, action.response); | |||
case STATUS_IMPORT: | |||
return importStatus(state, action.status); | |||
case STATUSES_IMPORT: | |||
return importStatuses(state, action.statuses); | |||
case FAVOURITE_REQUEST: | |||
return state.setIn([action.status.get('id'), 'favourited'], true); | |||
case FAVOURITE_FAIL: | |||
@@ -126,16 +57,6 @@ export default function statuses(state = initialState, action) { | |||
return state.withMutations(map => { | |||
action.ids.forEach(id => map.setIn([id, 'hidden'], true)); | |||
}); | |||
case TIMELINE_REFRESH_SUCCESS: | |||
case TIMELINE_EXPAND_SUCCESS: | |||
case CONTEXT_FETCH_SUCCESS: | |||
case NOTIFICATIONS_REFRESH_SUCCESS: | |||
case NOTIFICATIONS_EXPAND_SUCCESS: | |||
case FAVOURITED_STATUSES_FETCH_SUCCESS: | |||
case FAVOURITED_STATUSES_EXPAND_SUCCESS: | |||
case PINNED_STATUSES_FETCH_SUCCESS: | |||
case SEARCH_FETCH_SUCCESS: | |||
return normalizeStatuses(state, action.statuses); | |||
case TIMELINE_DELETE: | |||
return deleteStatus(state, action.id, action.references); | |||
default: | |||