mirror of
https://github.com/mastodon/mastodon.git
synced 2024-12-23 17:44:24 +00:00
Store objects to IndexedDB (#6826)
This commit is contained in:
parent
28384c1771
commit
fe398a098e
|
@ -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));
|
||||
|
|
76
app/javascript/mastodon/actions/importer/index.js
Normal file
76
app/javascript/mastodon/actions/importer/index.js
Normal file
|
@ -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));
|
||||
};
|
||||
}
|
46
app/javascript/mastodon/actions/importer/normalizer.js
Normal file
46
app/javascript/mastodon/actions/importer/normalizer.js
Normal file
|
@ -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));
|
||||
|
|
28
app/javascript/mastodon/db/async.js
Normal file
28
app/javascript/mastodon/db/async.js
Normal file
|
@ -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');
|
||||
};
|
||||
});
|
93
app/javascript/mastodon/db/modifier.js
Normal file
93
app/javascript/mastodon/db/modifier.js
Normal file
|
@ -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 importStatus = (state, status) => state.set(status.id, fromJS(status));
|
||||
|
||||
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;
|
||||
}, {});
|
||||
|
||||
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:
|
||||
|
|
Loading…
Reference in a new issue