Compare commits

...

4 commits

Author SHA1 Message Date
Alex Gleason 827cea7417
Get rid of STATUS_IMPORT in status_reducer 2021-07-15 17:01:10 -05:00
Alex Gleason 71398364a7
Remove account_counters reducer 2021-07-15 16:29:17 -05:00
Alex Gleason 80155746d5
Remove account importer, ACCOUNT_IMPORT, and fetchImportedAccount 2021-07-15 16:12:56 -05:00
Alex Gleason 66ed5ac58f
Remove unused IndexedDB reads
https://github.com/mastodon/mastodon/pull/14730
2021-07-15 12:41:46 -05:00
34 changed files with 505 additions and 795 deletions

View file

@ -1,11 +1,5 @@
import api, { getLinks } from '../api';
import openDB from '../storage/db';
import {
importAccount,
importFetchedAccount,
importFetchedAccounts,
importErrorWhileFetchingAccountByUsername,
} from './importer';
import { importErrorWhileFetchingAccountByUsername } from './importer';
import { isLoggedIn } from 'soapbox/utils/auth';
export const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST';
@ -96,24 +90,6 @@ export const NOTIFICATION_SETTINGS_REQUEST = 'NOTIFICATION_SETTINGS_REQUEST';
export const NOTIFICATION_SETTINGS_SUCCESS = 'NOTIFICATION_SETTINGS_SUCCESS';
export const NOTIFICATION_SETTINGS_FAIL = 'NOTIFICATION_SETTINGS_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 createAccount(params) {
return (dispatch, getState) => {
dispatch({ type: ACCOUNT_CREATE_REQUEST, params });
@ -138,18 +114,8 @@ export function fetchAccount(id) {
dispatch(fetchAccountRequest(id));
openDB().then(db => getFromDB(
dispatch,
getState,
db.transaction('accounts', 'read').objectStore('accounts').index('id'),
id,
).then(() => db.close(), error => {
db.close();
throw error;
})).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => {
dispatch(importFetchedAccount(response.data));
})).then(() => {
dispatch(fetchAccountSuccess());
api(getState).get(`/api/v1/accounts/${id}`).then(response => {
dispatch(fetchAccountSuccess(response.data));
}).catch(error => {
dispatch(fetchAccountFail(id, error));
});
@ -167,9 +133,7 @@ export function fetchAccountByUsername(username) {
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data));
}).then(() => {
dispatch(fetchAccountSuccess());
dispatch(fetchAccountSuccess(response.data));
}).catch(error => {
dispatch(fetchAccountFail(null, error));
dispatch(importErrorWhileFetchingAccountByUsername(username));
@ -184,9 +148,10 @@ export function fetchAccountRequest(id) {
};
};
export function fetchAccountSuccess() {
export function fetchAccountSuccess(account) {
return {
type: ACCOUNT_FETCH_SUCCESS,
account,
};
};
@ -507,7 +472,6 @@ 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 => {
@ -555,7 +519,6 @@ 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 => {
@ -597,7 +560,6 @@ 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 => {
@ -645,7 +607,6 @@ 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 => {
@ -731,7 +692,6 @@ 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)));
};
@ -772,7 +732,6 @@ 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)));
};

View file

@ -1,5 +1,5 @@
import api from '../api';
import { importFetchedAccount, importFetchedStatuses } from 'soapbox/actions/importer';
import { importFetchedStatuses } from 'soapbox/actions/importer';
import { fetchRelationships } from 'soapbox/actions/accounts';
export const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST';
@ -97,8 +97,6 @@ export function fetchReports(params) {
.get('/api/pleroma/admin/reports', { params })
.then(({ data: { reports } }) => {
reports.forEach(report => {
dispatch(importFetchedAccount(report.account));
dispatch(importFetchedAccount(report.actor));
dispatch(importFetchedStatuses(report.statuses));
});
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports, params });

View file

@ -1,6 +1,5 @@
import { defineMessages } from 'react-intl';
import api, { baseClient } from '../api';
import { importFetchedAccount } from './importer';
import snackbar from 'soapbox/actions/snackbar';
import { createAccount } from 'soapbox/actions/accounts';
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
@ -145,7 +144,6 @@ export function verifyCredentials(token) {
dispatch({ type: VERIFY_CREDENTIALS_REQUEST });
return baseClient(token).get('/api/v1/accounts/verify_credentials').then(({ data: account }) => {
dispatch(importFetchedAccount(account));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
if (account.id === getState().get('me')) dispatch(fetchMeSuccess(account));
return account;

View file

@ -1,6 +1,5 @@
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { isLoggedIn } from 'soapbox/utils/auth';
import { getNextLinkName } from 'soapbox/utils/quirks';
@ -21,7 +20,6 @@ export function fetchBlocks() {
api(getState).get('/api/v1/blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
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)));
@ -64,7 +62,6 @@ export function expandBlocks() {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
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)));

View file

@ -5,7 +5,6 @@ import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light
import { tagHistory } from '../settings';
import { useEmoji } from './emojis';
import resizeImage from '../utils/resize_image';
import { importFetchedAccounts } from './importer';
import { updateTimeline, dequeueTimeline } from './timelines';
import { showAlert, showAlertForError } from './alerts';
import { defineMessages } from 'react-intl';
@ -388,7 +387,6 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
limit: 4,
},
}).then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(readyComposeSuggestionsAccounts(token, response.data));
}).catch(error => {
if (!isCancel(error)) {

View file

@ -1,6 +1,5 @@
import api, { getLinks } from '../api';
import {
importFetchedAccounts,
importFetchedStatuses,
importFetchedStatus,
} from './importer';
@ -52,7 +51,6 @@ export const expandConversations = ({ maxId } = {}) => (dispatch, getState) => {
.then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.reduce((aggr, item) => aggr.concat(item.accounts), [])));
dispatch(importFetchedStatuses(response.data.map(item => item.last_status).filter(x => !!x)));
dispatch(expandConversationsSuccess(response.data, next ? next.uri : null, isLoadingRecent));
})
@ -76,8 +74,6 @@ export const expandConversationsFail = error => ({
});
export const updateConversations = conversation => dispatch => {
dispatch(importFetchedAccounts(conversation.accounts));
if (conversation.last_status) {
dispatch(importFetchedStatus(conversation.last_status));
}

View file

@ -1,5 +1,5 @@
import api from '../api';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { importFetchedStatus } from './importer';
import { favourite, unfavourite } from './interactions';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -54,9 +54,6 @@ export function fetchEmojiReacts(id, emoji) {
: `/api/v1/pleroma/statuses/${id}/reactions`;
return api(getState).get(url).then(response => {
response.data.forEach(emojiReact => {
dispatch(importFetchedAccounts(emojiReact.accounts));
});
dispatch(fetchEmojiReactsSuccess(id, response.data));
}).catch(error => {
dispatch(fetchEmojiReactsFail(id, error));

View file

@ -1,5 +1,4 @@
import api, { getLinks } from '../api';
import { importFetchedAccounts } from './importer';
import { fetchRelationships } from './accounts';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -235,7 +234,6 @@ export function fetchMembers(id) {
api(getState).get(`/api/v1/groups/${id}/accounts`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchMembersSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => {
@ -283,7 +281,6 @@ export function expandMembers(id) {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandMembersSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => {
@ -325,7 +322,6 @@ export function fetchRemovedAccounts(id) {
api(getState).get(`/api/v1/groups/${id}/removed_accounts`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(fetchRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => {
@ -373,7 +369,6 @@ export function expandRemovedAccounts(id) {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data));
dispatch(expandRemovedAccountsSuccess(id, response.data, next ? next.uri : null));
dispatch(fetchRelationships(response.data.map(item => item.id)));
}).catch(error => {

View file

@ -1,12 +1,9 @@
import { getSettings } from '../settings';
import {
normalizeAccount,
normalizeStatus,
normalizePoll,
} 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';
export const POLLS_IMPORT = 'POLLS_IMPORT';
@ -18,14 +15,6 @@ function pushUnique(array, 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 };
}
@ -38,28 +27,6 @@ export function importPolls(polls) {
return { type: POLLS_IMPORT, polls };
}
export function importFetchedAccount(account) {
return importFetchedAccounts([account]);
}
export function importFetchedAccounts(accounts) {
const normalAccounts = [];
function processAccount(account) {
if (!account.id) return;
pushUnique(normalAccounts, normalizeAccount(account));
if (account.moved) {
processAccount(account.moved);
}
}
accounts.forEach(processAccount);
return importAccounts(normalAccounts);
}
export function importFetchedStatus(status) {
return importFetchedStatuses([status]);
}
@ -108,7 +75,6 @@ export function importFetchedStatuses(statuses) {
statuses.forEach(processStatus);
dispatch(importPolls(polls));
dispatch(importFetchedAccounts(accounts));
dispatch(importStatuses(normalStatuses));
};
}

View file

@ -1,6 +1,5 @@
import escapeTextContentForBrowser from 'escape-html';
import emojify from '../../features/emoji/emoji';
import { unescapeHTML } from '../../utils/html';
const domParser = new DOMParser();
@ -9,31 +8,6 @@ const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
return obj;
}, {});
export function normalizeAccount(account) {
account = { ...account };
const emojiMap = makeEmojiMap(account);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.note_emojified = emojify(account.note, emojiMap);
if (account.fields) {
account.fields = account.fields.map(pair => ({
...pair,
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value),
}));
}
if (account.moved) {
account.moved = account.moved.id;
}
return account;
}
export function normalizeStatus(status, normalOldStatus, expandSpoilers) {
const normalStatus = { ...status };

View file

@ -1,6 +1,6 @@
import { defineMessages } from 'react-intl';
import api from '../api';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { importFetchedStatus } from './importer';
import snackbar from 'soapbox/actions/snackbar';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -139,7 +139,7 @@ export function favourite(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then(function(response) {
dispatch(importFetchedStatus(response.data));
dispatch(favouriteSuccess(status));
dispatch(favouriteSuccess(response.data));
}).catch(function(error) {
dispatch(favouriteFail(status, error));
});
@ -154,7 +154,7 @@ export function unfavourite(status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unfavouriteSuccess(status));
dispatch(unfavouriteSuccess(response.data));
}).catch(error => {
dispatch(unfavouriteFail(status, error));
});
@ -217,7 +217,7 @@ export function bookmark(intl, status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function(response) {
dispatch(importFetchedStatus(response.data));
dispatch(bookmarkSuccess(status, response.data));
dispatch(bookmarkSuccess(response.data));
dispatch(snackbar.success(intl.formatMessage(messages.bookmarkAdded)));
}).catch(function(error) {
dispatch(bookmarkFail(status, error));
@ -231,7 +231,7 @@ export function unbookmark(intl, status) {
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(unbookmarkSuccess(status, response.data));
dispatch(unbookmarkSuccess(response.data));
dispatch(snackbar.success(intl.formatMessage(messages.bookmarkRemoved)));
}).catch(error => {
dispatch(unbookmarkFail(status, error));
@ -246,11 +246,10 @@ export function bookmarkRequest(status) {
};
};
export function bookmarkSuccess(status, response) {
export function bookmarkSuccess(status) {
return {
type: BOOKMARK_SUCCESS,
status: status,
response: response,
};
};
@ -269,11 +268,10 @@ export function unbookmarkRequest(status) {
};
};
export function unbookmarkSuccess(status, response) {
export function unbookmarkSuccess(status) {
return {
type: UNBOOKMARK_SUCCESS,
status: status,
response: response,
};
};
@ -292,7 +290,6 @@ 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));
@ -329,7 +326,6 @@ 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));

View file

@ -1,5 +1,4 @@
import api from '../api';
import { importFetchedAccounts } from './importer';
import { showAlertForError } from './alerts';
import { isLoggedIn } from 'soapbox/utils/auth';
@ -223,7 +222,6 @@ export const fetchListAccounts = listId => (dispatch, getState) => {
dispatch(fetchListAccountsRequest(listId));
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)));
};
@ -257,7 +255,6 @@ export const fetchListSuggestions = q => (dispatch, getState) => {
};
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
dispatch(importFetchedAccounts(data));
dispatch(fetchListSuggestionsReady(q, data));
}).catch(error => dispatch(showAlertForError(error)));
};

View file

@ -1,5 +1,4 @@
import api from '../api';
import { importFetchedAccount } from './importer';
import { verifyCredentials } from './auth';
export const ME_FETCH_REQUEST = 'ME_FETCH_REQUEST';
@ -52,7 +51,6 @@ export function fetchMeRequest() {
export function fetchMeSuccess(me) {
return (dispatch, getState) => {
dispatch(importFetchedAccount(me));
dispatch({
type: ME_FETCH_SUCCESS,
me,
@ -76,7 +74,6 @@ export function patchMeRequest() {
export function patchMeSuccess(me) {
return (dispatch, getState) => {
dispatch(importFetchedAccount(me));
dispatch({
type: ME_PATCH_SUCCESS,
me,

View file

@ -1,6 +1,5 @@
import api, { getLinks } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { openModal } from './modal';
import { isLoggedIn } from 'soapbox/utils/auth';
import { getNextLinkName } from 'soapbox/utils/quirks';
@ -25,7 +24,6 @@ export function fetchMutes() {
api(getState).get('/api/v1/mutes').then(response => {
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
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)));
@ -68,7 +66,6 @@ export function expandMutes() {
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === nextLinkName);
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)));

View file

@ -3,8 +3,6 @@ import IntlMessageFormat from 'intl-messageformat';
import 'intl-pluralrules';
import { fetchRelationships } from './accounts';
import {
importFetchedAccount,
importFetchedAccounts,
importFetchedStatus,
importFetchedStatuses,
} from './importer';
@ -52,19 +50,10 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
}
};
export function updateNotifications(notification, intlMessages, intlLocale) {
function updateNotifications(notification, intlMessages, intlLocale) {
return (dispatch, getState) => {
const showInColumn = getSettings(getState()).getIn(['notifications', 'shows', notification.type], true);
if (notification.account) {
dispatch(importFetchedAccount(notification.account));
}
// Used by Move notification
if (notification.target) {
dispatch(importFetchedAccount(notification.target));
}
if (notification.status) {
dispatch(importFetchedStatus(notification.status));
}
@ -196,15 +185,6 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
const next = getLinks(response).refs.find(link => link.rel === 'next');
const entries = response.data.reduce((acc, item) => {
if (item.account && item.account.id) {
acc.accounts[item.account.id] = item.account;
}
// Used by Move notification
if (item.target && item.target.id) {
acc.accounts[item.target.id] = item.target;
}
if (item.status && item.status.id) {
acc.statuses[item.status.id] = item.status;
}
@ -212,7 +192,6 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
return acc;
}, { accounts: {}, statuses: {} });
dispatch(importFetchedAccounts(Object.values(entries.accounts)));
dispatch(importFetchedStatuses(Object.values(entries.statuses)));
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore));

View file

@ -1,6 +1,6 @@
import api from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
import { importFetchedStatuses } from './importer';
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
export const SEARCH_CLEAR = 'SEARCH_CLEAR';
@ -40,10 +40,6 @@ export function submitSearch() {
limit: 20,
},
}).then(response => {
if (response.data.accounts) {
dispatch(importFetchedAccounts(response.data.accounts));
}
if (response.data.statuses) {
dispatch(importFetchedStatuses(response.data.statuses));
}

View file

@ -1,6 +1,4 @@
import api from '../api';
import openDB from '../storage/db';
import { evictStatus } from '../storage/modifier';
import { deleteFromTimelines } from './timelines';
import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer';
import { openModal } from './modal';
@ -59,48 +57,6 @@ export function createStatus(params, idempotencyKey) {
};
};
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;
@ -113,31 +69,19 @@ export function fetchStatus(id) {
dispatch(fetchStatusRequest(id, skipLoading));
openDB().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(() => {
db.close();
}, error => {
db.close();
throw error;
});
}).then(() => {
dispatch(fetchStatusSuccess(skipLoading));
}, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => {
api(getState).get(`/api/v1/statuses/${id}`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(fetchStatusSuccess(skipLoading));
})).catch(error => {
dispatch(fetchStatusSuccess(response.data, skipLoading));
}).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading));
});
};
};
export function fetchStatusSuccess(skipLoading) {
export function fetchStatusSuccess(status, skipLoading) {
return {
type: STATUS_FETCH_SUCCESS,
status,
skipLoading,
};
};
@ -173,7 +117,6 @@ export function deleteStatus(id, routerHistory, withRedraft = false) {
dispatch(deleteStatusRequest(id));
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
evictStatus(id);
dispatch(deleteStatusSuccess(id));
dispatch(deleteFromTimelines(id));

View file

@ -14,6 +14,8 @@ import messages from 'soapbox/locales/messages';
export const STREAMING_CHAT_UPDATE = 'STREAMING_CHAT_UPDATE';
export const STREAMING_FOLLOW_RELATIONSHIPS_UPDATE = 'STREAMING_FOLLOW_RELATIONSHIPS_UPDATE';
export const STREAMING_NOTIFICATION_UPDATE = 'STREAMING_NOTIFICATION_UPDATE';
export const STREAMING_TIMELINE_UPDATE = 'STREAMING_TIMELINE_UPDATE';
const validLocale = locale => Object.keys(messages).includes(locale);
@ -33,10 +35,81 @@ function updateFollowRelationships(relationships) {
};
}
function updateChat(chat) {
return (dispatch, getState) => {
const me = getState().get('me');
const messageOwned = !(chat.last_message && chat.last_message.account_id !== me);
dispatch({
type: STREAMING_CHAT_UPDATE,
chat,
me,
// Only play sounds for recipient messages
meta: !messageOwned && getSettings(getState()).getIn(['chats', 'sound']) && { sound: 'chat' },
});
};
};
function updateNotification(notification) {
return (dispatch, getState) => {
const locale = getLocale(getState());
dispatch({
type: STREAMING_NOTIFICATION_UPDATE,
notification,
});
messages[locale]().then(messages => {
dispatch(updateNotificationsQueue(notification, messages, locale, window.location.pathname));
}).catch(error => {
console.error(error);
});
};
};
function updateTimeline(timelineId, status, accept) {
return (dispatch, getState) => {
dispatch({
type: STREAMING_TIMELINE_UPDATE,
timelineId,
status,
});
dispatch(processTimelineUpdate(timelineId, status, accept));
};
};
function handleEvent(timelineId, { event, payload }, accept) {
return (dispatch, getState) => {
switch(event) {
case 'update':
dispatch(updateTimeline(timelineId, JSON.parse(payload), accept));
break;
case 'delete':
dispatch(deleteFromTimelines(payload));
break;
case 'notification':
dispatch(updateNotification(JSON.parse(payload)));
break;
case 'conversation':
dispatch(updateConversations(JSON.parse(payload)));
break;
case 'filters_changed':
dispatch(fetchFilters());
break;
case 'pleroma:chat_update':
dispatch(updateChat(JSON.parse(payload)));
break;
case 'pleroma:follow_relationships_update':
dispatch(updateFollowRelationships(JSON.parse(payload)));
break;
}
};
};
export function connectTimelineStream(timelineId, path, pollingRefresh = null, accept = null) {
return connectStream (path, pollingRefresh, (dispatch, getState) => {
const locale = getLocale(getState());
return {
onConnect() {
@ -48,45 +121,7 @@ export function connectTimelineStream(timelineId, path, pollingRefresh = null, a
},
onReceive(data) {
switch(data.event) {
case 'update':
dispatch(processTimelineUpdate(timelineId, JSON.parse(data.payload), accept));
break;
case 'delete':
dispatch(deleteFromTimelines(data.payload));
break;
case 'notification':
messages[locale]().then(messages => {
dispatch(updateNotificationsQueue(JSON.parse(data.payload), messages, locale, window.location.pathname));
}).catch(error => {
console.error(error);
});
break;
case 'conversation':
dispatch(updateConversations(JSON.parse(data.payload)));
break;
case 'filters_changed':
dispatch(fetchFilters());
break;
case 'pleroma:chat_update':
dispatch((dispatch, getState) => {
const chat = JSON.parse(data.payload);
const me = getState().get('me');
const messageOwned = !(chat.last_message && chat.last_message.account_id !== me);
dispatch({
type: STREAMING_CHAT_UPDATE,
chat,
me,
// Only play sounds for recipient messages
meta: !messageOwned && getSettings(getState()).getIn(['chats', 'sound']) && { sound: 'chat' },
});
});
break;
case 'pleroma:follow_relationships_update':
dispatch(updateFollowRelationships(JSON.parse(data.payload)));
break;
}
dispatch(handleEvent(timelineId, data, accept));
},
};
});

View file

@ -1,5 +1,4 @@
import api from '../api';
import { importFetchedAccounts } from './importer';
import { isLoggedIn } from 'soapbox/utils/auth';
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST';
@ -13,7 +12,6 @@ export function fetchSuggestions() {
dispatch(fetchSuggestionsRequest());
api(getState).get('/api/v1/suggestions').then(response => {
dispatch(importFetchedAccounts(response.data));
dispatch(fetchSuggestionsSuccess(response.data));
}).catch(error => dispatch(fetchSuggestionsFail(error)));
};

View file

@ -9,31 +9,6 @@ describe('accounts reducer', () => {
expect(reducer(undefined, {})).toEqual(ImmutableMap());
});
// fails to add normalized accounts to state
// it('should handle ACCOUNT_IMPORT', () => {
// const state = ImmutableMap({ });
// const account = take(accounts, 1);
// const action = {
// type: actions.ACCOUNT_IMPORT,
// account: account,
// };
// debugger;
// expect(reducer(state, action).toJS()).toMatchObject({
// });
// });
// fails to add normalized accounts to state
// it('should handle ACCOUNTS_IMPORT', () => {
// const state = ImmutableMap({ });
// const accounts = take(accounts, 2);
// const action = {
// type: actions.ACCOUNTS_IMPORT,
// accounts: accounts,
// };
// expect(reducer(state, action).toJS()).toMatchObject({
// });
// });
//
// it('should handle ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP', () => {
// const state = ImmutableMap({ username: 'curtis' });
// const action = {

View file

@ -1,37 +0,0 @@
import reducer from '../accounts_counters';
import { Map as ImmutableMap } from 'immutable';
// import { ACCOUNT_FOLLOW_SUCCESS, ACCOUNT_UNFOLLOW_SUCCESS } from 'soapbox/actions/accounts';
// import relationship from 'soapbox/__fixtures__/relationship.json';
// import accounts_counter_initial from 'soapbox/__fixtures__/accounts_counter_initial.json';
// import accounts_counter_unfollow from 'soapbox/__fixtures__/accounts_counter_unfollow.json';
// import accounts_counter_follow from 'soapbox/__fixtures__/accounts_counter_follow.json';
describe('accounts_counters reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual(ImmutableMap());
});
// it('should handle ACCOUNT_FOLLOW_SUCCESS', () => {
// const state = ImmutableList([accounts_counter_initial]);
// const action = {
// type: ACCOUNT_FOLLOW_SUCCESS,
// relationship: relationship,
// alreadyFollowing: false,
// };
// expect(reducer(state, action)).toEqual(
// ImmutableList([ accounts_counter_follow ]));
// });
//
// it('should handle ACCOUNT_UNFOLLOW_SUCCESS', () => {
// const state = ImmutableList([accounts_counter_initial]);
// const action = {
// type: ACCOUNT_UNFOLLOW_SUCCESS,
// relationship: relationship,
// alreadyFollowing: true,
// };
// expect(reducer(state, action)).toEqual(
// ImmutableList([accounts_counter_unfollow]));
// });
});

View file

@ -1,38 +1,8 @@
import reducer from '../relationships';
import {
ACCOUNT_IMPORT,
} from '../../actions/importer';
import lain from 'soapbox/__fixtures__/lain.json';
import { Map as ImmutableMap, fromJS } from 'immutable';
import { Map as ImmutableMap } from 'immutable';
describe('relationships reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {})).toEqual(ImmutableMap());
});
describe('ACCOUNT_IMPORT', () => {
it('should import the relationship', () => {
const action = {
type: ACCOUNT_IMPORT,
account: lain,
};
const state = ImmutableMap();
expect(reducer(state, action)).toEqual(fromJS({
'9v5bqYwY2jfmvPNhTM': {
blocked_by: false,
blocking: false,
domain_blocking: false,
endorsed: false,
followed_by: true,
following: true,
id: '9v5bqYwY2jfmvPNhTM',
muting: false,
muting_notifications: false,
requested: false,
showing_reblogs: true,
subscribing: false,
},
}));
});
});
});

View file

@ -1,17 +1,90 @@
import {
ACCOUNT_IMPORT,
ACCOUNTS_IMPORT,
ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP,
} from '../actions/importer';
import { CHATS_FETCH_SUCCESS, CHAT_FETCH_SUCCESS } from 'soapbox/actions/chats';
import { STREAMING_CHAT_UPDATE } from 'soapbox/actions/streaming';
import { normalizeAccount as normalizeAccount2 } from 'soapbox/actions/importer/normalizer';
} from 'soapbox/actions/importer';
import {
Map as ImmutableMap,
List as ImmutableList,
fromJS,
} from 'immutable';
import { normalizePleromaUserFields } from 'soapbox/utils/pleroma';
ACCOUNT_FETCH_SUCCESS,
FOLLOWERS_FETCH_SUCCESS,
FOLLOWERS_EXPAND_SUCCESS,
FOLLOWING_FETCH_SUCCESS,
FOLLOWING_EXPAND_SUCCESS,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_EXPAND_SUCCESS,
} from 'soapbox/actions/accounts';
import {
VERIFY_CREDENTIALS_SUCCESS,
} from 'soapbox/actions/auth';
import {
ME_FETCH_SUCCESS,
ME_PATCH_SUCCESS,
} from 'soapbox/actions/me';
import {
BLOCKS_FETCH_SUCCESS,
BLOCKS_EXPAND_SUCCESS,
} from 'soapbox/actions/blocks';
import {
MUTES_FETCH_SUCCESS,
MUTES_EXPAND_SUCCESS,
} from 'soapbox/actions/mutes';
import {
COMPOSE_SUGGESTIONS_READY,
} from 'soapbox/actions/compose';
import {
REBLOG_SUCCESS,
UNREBLOG_SUCCESS,
FAVOURITE_SUCCESS,
UNFAVOURITE_SUCCESS,
REBLOGS_FETCH_SUCCESS,
FAVOURITES_FETCH_SUCCESS,
BOOKMARK_SUCCESS,
UNBOOKMARK_SUCCESS,
} from 'soapbox/actions/interactions';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_EXPAND_SUCCESS,
} from 'soapbox/actions/timelines';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
} from 'soapbox/actions/statuses';
import { SEARCH_FETCH_SUCCESS } from 'soapbox/actions/search';
import {
CONVERSATIONS_FETCH_SUCCESS,
CONVERSATIONS_UPDATE,
} from 'soapbox/actions/conversations';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
} from 'soapbox/actions/notifications';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from 'soapbox/actions/favourites';
import {
LIST_ACCOUNTS_FETCH_SUCCESS,
LIST_EDITOR_SUGGESTIONS_READY,
} from 'soapbox/actions/lists';
import {
SUGGESTIONS_FETCH_SUCCESS,
} from 'soapbox/actions/suggestions';
import {
CHATS_FETCH_SUCCESS,
CHAT_FETCH_SUCCESS,
} from 'soapbox/actions/chats';
import {
EMOJI_REACTS_FETCH_SUCCESS,
} from 'soapbox/actions/emoji_reacts';
import {
GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS,
GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS,
GROUP_MEMBERS_FETCH_SUCCESS,
GROUP_MEMBERS_EXPAND_SUCCESS,
} from 'soapbox/actions/groups';
import {
STREAMING_NOTIFICATION_UPDATE,
STREAMING_CHAT_UPDATE,
STREAMING_TIMELINE_UPDATE,
} from 'soapbox/actions/streaming';
import {
ADMIN_USERS_FETCH_SUCCESS,
ADMIN_USERS_TAG_REQUEST,
@ -26,48 +99,165 @@ import {
ADMIN_REMOVE_PERMISSION_GROUP_REQUEST,
ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS,
ADMIN_REMOVE_PERMISSION_GROUP_FAIL,
} from 'soapbox/actions/admin';
import {
ADMIN_USERS_DELETE_REQUEST,
ADMIN_USERS_DELETE_FAIL,
ADMIN_USERS_DEACTIVATE_REQUEST,
ADMIN_USERS_DEACTIVATE_FAIL,
ADMIN_REPORTS_FETCH_SUCCESS,
} from 'soapbox/actions/admin';
import {
Map as ImmutableMap,
List as ImmutableList,
fromJS,
} from 'immutable';
import { normalizePleromaUserFields } from 'soapbox/utils/pleroma';
import escapeTextContentForBrowser from 'escape-html';
import emojify from 'soapbox/features/emoji/emoji';
import { unescapeHTML } from 'soapbox/utils/html';
const initialState = ImmutableMap();
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
const normalizePleroma = account => {
if (!account.pleroma) return account;
account.pleroma = normalizePleromaUserFields(account.pleroma);
delete account.pleroma.chat_token;
if (account.pleroma) {
account.pleroma = normalizePleromaUserFields(account.pleroma);
delete account.pleroma.chat_token;
}
return account;
};
const normalizeAccount = (state, account) => {
const normalized = fromJS(normalizePleroma(account)).deleteAll([
'followers_count',
'following_count',
'statuses_count',
]);
const normalizeAccount = account => {
account = { ...account };
return state.set(account.id, normalized);
if (!account.id) {
throw 'missing ID property';
}
const emojiMap = makeEmojiMap(account);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.note_emojified = emojify(account.note, emojiMap);
if (account.fields) {
account.fields = account.fields.map(pair => ({
...pair,
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value),
}));
}
if (account.moved) {
account.moved = account.moved.id;
}
account = normalizePleroma(account);
return fromJS(account);
};
const normalizeAccounts = (state, accounts) => {
accounts.forEach(account => {
state = normalizeAccount(state, account);
const importAccount = (state, account) => {
return state.withMutations(state => {
if (account.moved) {
importAccount(state, account.moved);
}
try {
state.set(account.id, normalizeAccount(account));
} catch(e) {
// Skip broken accounts
console.warn(`Skipped broken account returned from the API: ${e}`);
console.warn(account);
}
});
return state;
};
const importAccountFromChat = (state, chat) =>
// TODO: Fix this monstrosity
normalizeAccount(state, normalizeAccount2(chat.account));
const importAccounts = (state, accounts) => {
return state.withMutations(state => {
accounts.forEach(account => {
importAccount(state, account);
});
});
};
const importAccountsFromChats = (state, chats) =>
state.withMutations(mutable =>
chats.forEach(chat => importAccountFromChat(mutable, chat)));
const importStatus = (state, status) => {
return state.withMutations(state => {
importAccount(state, status.account);
if (status.reblog && status.reblog.account) {
importAccount(state, status.reblog.account);
}
});
};
const importStatuses = (state, statuses) => {
return state.withMutations(state => {
statuses.forEach(status => importStatus(state, status));
});
};
const importContext = (state, ancestors, descendants) => {
return state.withMutations(state => {
importStatuses(state, ancestors);
importStatuses(state, descendants);
});
};
const importNotification = (state, notification) => (
importAccount(state, notification.account)
);
const importNotifications = (state, notifications) => {
return state.withMutations(state => {
notifications.forEach(notification => importNotification(state, notification));
});
};
const importConversation = (state, conversation) => (
importAccounts(conversation.accounts)
);
const importConversations = (state, conversations) => {
return state.withMutations(state => {
conversations.forEach(conversation => importConversation(state, conversation));
});
};
const importChat = (state, chat) => importAccount(state, chat.account);
const importChats = (state, chats) => {
return state.withMutations(state => {
chats.forEach(chat => importChat(state, chat));
});
};
const importEmojiReact = (state, emojiReact) => (
importAccounts(emojiReact.accounts)
);
const importEmojiReacts = (state, emojiReacts) => {
return state.withMutations(state => {
emojiReacts.forEach(emojiReact => importEmojiReact(state, emojiReact));
});
};
const importReport = (state, report) => {
return state.withMutations(state => {
importAccount(report.account);
importAccount(report.actor);
});
};
const importAdminReports = (state, reports) => {
return state.withMutations(state => {
reports.forEach(report => importReport(state, report));
});
};
const addTags = (state, accountIds, tags) => {
return state.withMutations(state => {
@ -188,19 +378,72 @@ const importAdminUsers = (state, adminUsers) => {
export default function accounts(state = initialState, action) {
switch(action.type) {
case ACCOUNT_IMPORT:
return normalizeAccount(state, action.account);
case ACCOUNTS_IMPORT:
return normalizeAccounts(state, action.accounts);
case ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP:
return state.set(-1, ImmutableMap({
username: action.username,
}));
case ACCOUNT_FETCH_SUCCESS:
case VERIFY_CREDENTIALS_SUCCESS:
return importAccount(state, action.account);
case ME_FETCH_SUCCESS:
case ME_PATCH_SUCCESS:
return importAccount(state, action.me);
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:
case SUGGESTIONS_FETCH_SUCCESS:
case GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS:
case GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS:
case GROUP_MEMBERS_FETCH_SUCCESS:
case GROUP_MEMBERS_EXPAND_SUCCESS:
return action.accounts ? importAccounts(state, action.accounts) : state;
case CONVERSATIONS_FETCH_SUCCESS:
return importConversations(state, action.conversations);
case CONVERSATIONS_UPDATE:
return importConversation(state, action.conversation);
case NOTIFICATIONS_REFRESH_SUCCESS:
case NOTIFICATIONS_EXPAND_SUCCESS:
return importNotifications(state, action.notifications);
case NOTIFICATIONS_UPDATE:
case STREAMING_NOTIFICATION_UPDATE:
return importNotification(state, action.notification);
case SEARCH_FETCH_SUCCESS:
return importAccounts(state, action.results.accounts);
case TIMELINE_REFRESH_SUCCESS:
case TIMELINE_EXPAND_SUCCESS:
case FAVOURITED_STATUSES_FETCH_SUCCESS:
case FAVOURITED_STATUSES_EXPAND_SUCCESS:
return importStatuses(state, action.statuses);
case CONTEXT_FETCH_SUCCESS:
return importContext(state, action.ancestors, action.descendants);
case REBLOG_SUCCESS:
case FAVOURITE_SUCCESS:
case UNREBLOG_SUCCESS:
case UNFAVOURITE_SUCCESS:
case STREAMING_TIMELINE_UPDATE:
case STATUS_FETCH_SUCCESS:
case BOOKMARK_SUCCESS:
case UNBOOKMARK_SUCCESS:
return importStatus(state, action.status);
case CHATS_FETCH_SUCCESS:
return importAccountsFromChats(state, action.chats);
return importChats(state, action.chats);
case CHAT_FETCH_SUCCESS:
case STREAMING_CHAT_UPDATE:
return importAccountsFromChats(state, [action.chat]);
return importChats(state, [action.chat]);
case EMOJI_REACTS_FETCH_SUCCESS:
return importEmojiReacts(state, action.emojiReacts);
case ADMIN_USERS_TAG_REQUEST:
case ADMIN_USERS_TAG_SUCCESS:
case ADMIN_USERS_UNTAG_FAIL:
@ -225,6 +468,8 @@ export default function accounts(state = initialState, action) {
return setActive(state, action.accountIds, true);
case ADMIN_USERS_FETCH_SUCCESS:
return importAdminUsers(state, action.users);
case ADMIN_REPORTS_FETCH_SUCCESS:
return importAdminReports(state, action.reports);
default:
return state;
}

View file

@ -1,52 +0,0 @@
import {
ACCOUNT_FOLLOW_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
} from '../actions/accounts';
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
import { STREAMING_FOLLOW_RELATIONSHIPS_UPDATE } from 'soapbox/actions/streaming';
import { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeAccount = (state, account) => state.set(account.id, fromJS({
followers_count: account.followers_count,
following_count: account.following_count,
statuses_count: account.statuses_count,
}));
const normalizeAccounts = (state, accounts) => {
accounts.forEach(account => {
state = normalizeAccount(state, account);
});
return state;
};
const updateFollowCounters = (state, counterUpdates) => {
return state.withMutations(state => {
counterUpdates.forEach(counterUpdate => {
state.update(counterUpdate.id, ImmutableMap(), counters => counters.merge({
followers_count: counterUpdate.follower_count,
following_count: counterUpdate.following_count,
}));
});
});
};
const initialState = ImmutableMap();
export default function accountsCounters(state = initialState, action) {
switch(action.type) {
case ACCOUNT_IMPORT:
return normalizeAccount(state, action.account);
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);
case ACCOUNT_UNFOLLOW_SUCCESS:
return state.updateIn([action.relationship.id, 'followers_count'], num => Math.max(0, num - 1));
case STREAMING_FOLLOW_RELATIONSHIPS_UPDATE:
return updateFollowCounters(state, [action.follower, action.following]);
default:
return state;
}
};

View file

@ -4,7 +4,7 @@ import {
} from '../actions/accounts';
import { CONTEXT_FETCH_SUCCESS } from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from 'soapbox/actions/importer';
import { STREAMING_TIMELINE_UPDATE } from 'soapbox/actions/streaming';
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable';
const initialState = ImmutableMap({
@ -24,12 +24,6 @@ const importStatus = (state, { id, in_reply_to_id }) => {
});
};
const importStatuses = (state, statuses) => {
return state.withMutations(state => {
statuses.forEach(status => importStatus(state, status));
});
};
const insertTombstone = (state, ancestorId, descendantId) => {
const tombstoneId = `${descendantId}-tombstone`;
return state.withMutations(state => {
@ -100,10 +94,8 @@ export default function replies(state = initialState, action) {
return normalizeContext(state, action.id, action.ancestors, action.descendants);
case TIMELINE_DELETE:
return deleteStatuses(state, [action.id]);
case STATUS_IMPORT:
case STREAMING_TIMELINE_UPDATE:
return importStatus(state, action.status);
case STATUSES_IMPORT:
return importStatuses(state, action.statuses);
default:
return state;
}

View file

@ -10,7 +10,6 @@ import modal from './modal';
import user_lists from './user_lists';
import domain_lists from './domain_lists';
import accounts from './accounts';
import accounts_counters from './accounts_counters';
import statuses from './statuses';
import relationships from './relationships';
import settings from './settings';
@ -65,7 +64,6 @@ const appReducer = combineReducers({
domain_lists,
status_lists,
accounts,
accounts_counters,
statuses,
relationships,
settings,

View file

@ -15,17 +15,12 @@ import {
ACCOUNT_UNPIN_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts';
import {
ACCOUNT_IMPORT,
ACCOUNTS_IMPORT,
} from '../actions/importer';
import {
DOMAIN_BLOCK_SUCCESS,
DOMAIN_UNBLOCK_SUCCESS,
} from '../actions/domain_blocks';
import { STREAMING_FOLLOW_RELATIONSHIPS_UPDATE } from 'soapbox/actions/streaming';
import { Map as ImmutableMap, fromJS } from 'immutable';
import { get } from 'lodash';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
@ -45,21 +40,6 @@ const setDomainBlocking = (state, accounts, blocking) => {
});
};
const importPleromaAccount = (state, account) => {
const relationship = get(account, ['pleroma', 'relationship'], {});
if (relationship.id && relationship !== {})
return normalizeRelationship(state, relationship);
return state;
};
const importPleromaAccounts = (state, accounts) => {
accounts.forEach(account => {
state = importPleromaAccount(state, account);
});
return state;
};
const followStateToRelationship = followState => {
switch(followState) {
case 'follow_pending':
@ -82,10 +62,6 @@ const initialState = ImmutableMap();
export default function relationships(state = initialState, action) {
switch(action.type) {
case ACCOUNT_IMPORT:
return importPleromaAccount(state, action.account);
case ACCOUNTS_IMPORT:
return importPleromaAccounts(state, action.accounts);
case ACCOUNT_FOLLOW_REQUEST:
return state.setIn([action.id, 'requested'], true);
case ACCOUNT_FOLLOW_FAIL:

View file

@ -4,12 +4,14 @@ import {
SCHEDULED_STATUS_CANCEL_REQUEST,
SCHEDULED_STATUS_CANCEL_SUCCESS,
} from 'soapbox/actions/scheduled_statuses';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable';
const importStatus = (state, status) => {
if (!status.scheduled_at) return state;
return state.set(status.id, fromJS(status));
if (status.scheduled_at) {
return state.set(status.id, fromJS(status));
} else {
return state;
}
};
const importStatuses = (state, statuses) =>
@ -21,10 +23,8 @@ const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_IMPORT:
case STATUS_CREATE_SUCCESS:
return importStatus(state, action.status);
case STATUSES_IMPORT:
case SCHEDULED_STATUSES_FETCH_SUCCESS:
return importStatuses(state, action.statuses);
case SCHEDULED_STATUS_CANCEL_REQUEST:

View file

@ -1,45 +1,141 @@
import {
REBLOG_REQUEST,
REBLOG_SUCCESS,
REBLOG_FAIL,
UNREBLOG_SUCCESS,
FAVOURITE_REQUEST,
UNFAVOURITE_REQUEST,
FAVOURITE_SUCCESS,
FAVOURITE_FAIL,
} from '../actions/interactions';
UNFAVOURITE_REQUEST,
UNFAVOURITE_SUCCESS,
PIN_SUCCESS,
UNPIN_SUCCESS,
} from 'soapbox/actions/interactions';
import {
STATUS_FETCH_SUCCESS,
CONTEXT_FETCH_SUCCESS,
STATUS_MUTE_SUCCESS,
STATUS_UNMUTE_SUCCESS,
STATUS_REVEAL,
STATUS_HIDE,
} from '../actions/statuses';
} from 'soapbox/actions/statuses';
import {
EMOJI_REACT_REQUEST,
UNEMOJI_REACT_REQUEST,
} from '../actions/emoji_reacts';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
} from 'soapbox/actions/emoji_reacts';
import {
TIMELINE_REFRESH_SUCCESS,
TIMELINE_DELETE,
TIMELINE_EXPAND_SUCCESS,
} from 'soapbox/actions/timelines';
import {
NOTIFICATIONS_UPDATE,
NOTIFICATIONS_REFRESH_SUCCESS,
NOTIFICATIONS_EXPAND_SUCCESS,
} from 'soapbox/actions/notifications';
import {
STREAMING_TIMELINE_UPDATE,
} from 'soapbox/actions/streaming';
import {
FAVOURITED_STATUSES_FETCH_SUCCESS,
FAVOURITED_STATUSES_EXPAND_SUCCESS,
} from 'soapbox/actions/favourites';
import {
PINNED_STATUSES_FETCH_SUCCESS,
} from 'soapbox/actions/pin_statuses';
import { SEARCH_FETCH_SUCCESS } from '../actions/search';
import { Map as ImmutableMap, fromJS } from 'immutable';
import { simulateEmojiReact, simulateUnEmojiReact } from 'soapbox/utils/emoji_reacts';
import escapeTextContentForBrowser from 'escape-html';
import emojify from 'soapbox/features/emoji/emoji';
const importStatus = (state, status) => state.set(status.id, fromJS(status));
const domParser = new DOMParser();
const importStatuses = (state, statuses) =>
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status)));
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
export function normalizeStatus(status, normalOldStatus, expandSpoilers) {
const normalStatus = { ...status };
normalStatus.account = status.account.id;
if (status.reblog && status.reblog.id) {
normalStatus.reblog = status.reblog.id;
}
if (status.poll && status.poll.id) {
normalStatus.poll = status.poll.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 spoilerText = normalStatus.spoiler_text || '';
const searchContent = [spoilerText, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const emojiMap = makeEmojiMap(normalStatus);
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(normalStatus.content, emojiMap);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
}
return fromJS(normalStatus);
}
const importStatus = (state, status) => {
try {
return state.set(status.id, normalizeStatus(status));
} catch(e) {
// Skip broken statuses
console.warn(`Skipped broken status returned from the API: ${e}`);
console.warn(status);
return state;
}
};
const importStatuses = (state, statuses) => {
return state.withMutations(state => {
statuses.forEach(status => importStatus(state, status));
});
};
const deleteStatus = (state, id, references) => {
references.forEach(ref => {
state = deleteStatus(state, ref[0], []);
return state.withMutations(state => {
references.forEach(ref => deleteStatus(state, ref[0], []));
});
return state.delete(id);
};
const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_IMPORT:
case STREAMING_TIMELINE_UPDATE:
case STATUS_FETCH_SUCCESS:
case NOTIFICATIONS_UPDATE:
case REBLOG_SUCCESS:
case UNREBLOG_SUCCESS:
case FAVOURITE_SUCCESS:
case UNFAVOURITE_SUCCESS:
case PIN_SUCCESS:
case UNPIN_SUCCESS:
return importStatus(state, action.status);
case STATUSES_IMPORT:
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 importStatuses(state, action.statuses);
case FAVOURITE_REQUEST:
return state.update(action.status.get('id'), status =>

View file

@ -2,7 +2,6 @@ import { createSelector } from 'reselect';
import { List as ImmutableList } from 'immutable';
const getAccountBase = (state, id) => state.getIn(['accounts', id], null);
const getAccountCounters = (state, id) => state.getIn(['accounts_counters', id], null);
const getAccountRelationship = (state, id) => state.getIn(['relationships', id], null);
const getAccountMoved = (state, id) => state.getIn(['accounts', state.getIn(['accounts', id, 'moved'])]);
const getAccountAdminData = (state, id) => state.getIn(['admin', 'users', id]);
@ -14,17 +13,16 @@ const getAccountPatron = (state, id) => {
export const makeGetAccount = () => {
return createSelector([
getAccountBase,
getAccountCounters,
getAccountRelationship,
getAccountMoved,
getAccountAdminData,
getAccountPatron,
], (base, counters, relationship, moved, admin, patron) => {
], (base, relationship, moved, admin, patron) => {
if (base === null) {
return null;
}
return base.merge(counters).withMutations(map => {
return base.withMutations(map => {
map.set('relationship', relationship);
map.set('moved', moved);
map.set('patron', patron);

View file

@ -1,10 +1,5 @@
// import { freeStorage, storageFreeable } from '../storage/modifier';
import './web_push_notifications';
// function openSystemCache() {
// return caches.open('soapbox-system');
// }
function openWebCache() {
return caches.open('soapbox-web');
}
@ -13,9 +8,6 @@ function fetchRoot() {
return fetch('/', { credentials: 'include', redirect: 'manual' });
}
// const firefox = navigator.userAgent.match(/Firefox\/(\d+)/);
// const invalidOnlyIfCached = firefox && firefox[1] < 60;
// Cause a new version of a registered Service Worker to replace an existing one
// that is already installed, and replace the currently active worker on open pages.
self.addEventListener('install', function(event) {
@ -81,26 +73,5 @@ self.addEventListener('fetch', function(event) {
return response;
},
() => asyncCache.then(cache => cache.match('/'))));
} /* else if (storageFreeable && (ATTACHMENT_HOST ? url.host === ATTACHMENT_HOST : url.pathname.startsWith('/system/'))) {
event.respondWith(openSystemCache().then(cache => {
return cache.match(event.request.url).then(cached => {
if (cached === undefined) {
const asyncResponse = invalidOnlyIfCached && event.request.cache === 'only-if-cached' ?
fetch(event.request, { cache: 'no-cache' }) : fetch(event.request);
return asyncResponse.then(response => {
if (response.ok) {
cache
.put(event.request.url, response.clone())
.catch(()=>{}).then(freeStorage()).catch();
}
return response;
});
}
return cached;
});
}));
} */
}
});

View file

@ -1,27 +0,0 @@
export default () => new Promise((resolve, reject) => {
// ServiceWorker is required to synchronize the login state.
// 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 (!('caches' in self && 'getAll' in IDBObjectStore.prototype)) {
reject();
return;
}
const request = indexedDB.open('soapbox');
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');
};
});

View file

@ -1,211 +0,0 @@
import openDB from './db';
const accountAssetKeys = ['avatar', 'avatar_static', 'header', 'header_static'];
const storageMargin = 8388608;
const storeLimit = 1024;
// navigator.storage is not present on:
// Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.100 Safari/537.36 Edge/16.16299
// estimate method is not present on Chrome 57.0.2987.98 on Linux.
export const storageFreeable = 'storage' in navigator && 'estimate' in navigator.storage;
function openCache() {
// ServiceWorker and Cache API is not available on iOS 11
// https://webkit.org/status/#specification-service-workers
return self.caches ? caches.open('soapbox-system') : Promise.reject();
}
function printErrorIfAvailable(error) {
if (error) {
console.warn(error);
}
}
function put(name, objects, onupdate, oncreate) {
return openDB().then(db => (new Promise((resolve, reject) => {
const putTransaction = db.transaction(name, 'readwrite');
const putStore = putTransaction.objectStore(name);
const putIndex = putStore.index('id');
objects.forEach(object => {
putIndex.getKey(object.id).onsuccess = retrieval => {
function addObject() {
putStore.add(object);
}
function deleteObject() {
putStore.delete(retrieval.target.result).onsuccess = addObject;
}
if (retrieval.target.result) {
if (onupdate) {
onupdate(object, retrieval.target.result, putStore, deleteObject);
} else {
deleteObject();
}
} else {
if (oncreate) {
oncreate(object, addObject);
} else {
addObject();
}
}
};
});
putTransaction.oncomplete = () => {
const readTransaction = db.transaction(name, 'readonly');
const readStore = readTransaction.objectStore(name);
const count = readStore.count();
count.onsuccess = () => {
const excess = count.result - storeLimit;
if (excess > 0) {
const retrieval = readStore.getAll(null, excess);
retrieval.onsuccess = () => resolve(retrieval.result);
retrieval.onerror = reject;
} else {
resolve([]);
}
};
count.onerror = reject;
};
putTransaction.onerror = reject;
})).then(resolved => {
db.close();
return resolved;
}, error => {
db.close();
throw error;
}));
}
function evictAccountsByRecords(records) {
return openDB().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(record => {
openCache()
.then(cache => accountAssetKeys.forEach(key => cache.delete(records[key])))
.catch(printErrorIfAvailable);
accountsMovedIndex.getAll(record.id).onsuccess = ({ target }) => evict(target.result);
statusesIndex.getAll(record.id).onsuccess =
({ target }) => evictStatusesByRecords(target.result);
accountsIdIndex.getKey(record.id).onsuccess =
({ target }) => target.result && accounts.delete(target.result);
});
}
evict(records);
db.close();
}).catch(printErrorIfAvailable);
}
export function evictStatus(id) {
evictStatuses([id]);
}
export function evictStatuses(ids) {
return openDB().then(db => {
const transaction = db.transaction('statuses', 'readwrite');
const store = transaction.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);
});
db.close();
}).catch(printErrorIfAvailable);
}
function evictStatusesByRecords(records) {
return evictStatuses(records.map(({ id }) => id));
}
export function putAccounts(records, avatarStatic) {
const avatarKey = avatarStatic ? 'avatar_static' : 'avatar';
const newURLs = [];
put('accounts', records, (newRecord, oldKey, store, oncomplete) => {
store.get(oldKey).onsuccess = ({ target }) => {
accountAssetKeys.forEach(key => {
const newURL = newRecord[key];
const oldURL = target.result[key];
if (newURL !== oldURL) {
openCache()
.then(cache => cache.delete(oldURL))
.catch(printErrorIfAvailable);
}
});
const newURL = newRecord[avatarKey];
const oldURL = target.result[avatarKey];
if (newURL !== oldURL) {
newURLs.push(newURL);
}
oncomplete();
};
}, (newRecord, oncomplete) => {
newURLs.push(newRecord[avatarKey]);
oncomplete();
}).then(records => Promise.all([
evictAccountsByRecords(records),
openCache().then(cache => cache.addAll(newURLs)),
])).then(freeStorage, error => {
freeStorage();
throw error;
}).catch(printErrorIfAvailable);
}
export function putStatuses(records) {
put('statuses', records)
.then(evictStatusesByRecords)
.catch(printErrorIfAvailable);
}
export function freeStorage() {
return storageFreeable && navigator.storage.estimate().then(({ quota, usage }) => {
if (usage + storageMargin < quota) {
return null;
}
return openDB().then(db => new Promise((resolve, reject) => {
const retrieval = db.transaction('accounts', 'readonly').objectStore('accounts').getAll(null, 1);
retrieval.onsuccess = () => {
if (retrieval.result.length > 0) {
resolve(evictAccountsByRecords(retrieval.result).then(freeStorage));
} else {
resolve(caches.delete('soapbox-system'));
}
};
retrieval.onerror = reject;
db.close();
}));
});
}

View file

@ -45,7 +45,7 @@ export const isModerator = account => (
export const getFollowDifference = (state, accountId, type) => {
const listSize = state.getIn(['user_lists', type, accountId, 'items'], ImmutableList()).size;
const counter = state.getIn(['accounts_counters', accountId, `${type}_count`], 0);
const counter = state.getIn(['accounts', accountId, `${type}_count`], 0);
return Math.max(counter - listSize, 0);
};