Merge remote-tracking branch 'soapbox/develop' into familiar-followers
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
commit
2ec9cacb27
|
@ -57,6 +57,10 @@ export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST';
|
|||
export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS';
|
||||
export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL';
|
||||
|
||||
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST';
|
||||
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS';
|
||||
export const ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL';
|
||||
|
||||
export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST';
|
||||
export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS';
|
||||
export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL';
|
||||
|
@ -520,6 +524,42 @@ export function unsubscribeAccountFail(error) {
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
export function removeFromFollowers(id) {
|
||||
return (dispatch, getState) => {
|
||||
if (!isLoggedIn(getState)) return;
|
||||
|
||||
dispatch(muteAccountRequest(id));
|
||||
|
||||
api(getState).post(`/api/v1/accounts/${id}/remove_from_followers`).then(response => {
|
||||
dispatch(removeFromFollowersSuccess(response.data));
|
||||
}).catch(error => {
|
||||
dispatch(removeFromFollowersFail(id, error));
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function removeFromFollowersRequest(id) {
|
||||
return {
|
||||
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST,
|
||||
id,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeFromFollowersSuccess(relationship) {
|
||||
return {
|
||||
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
|
||||
relationship,
|
||||
};
|
||||
}
|
||||
|
||||
export function removeFromFollowersFail(error) {
|
||||
return {
|
||||
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchFollowers(id) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchFollowersRequest(id));
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
||||
export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS';
|
||||
export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL';
|
||||
|
||||
export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST';
|
||||
export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS';
|
||||
export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL';
|
||||
|
||||
export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST';
|
||||
export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS';
|
||||
export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' },
|
||||
followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' },
|
||||
mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' },
|
||||
});
|
||||
|
||||
function fileExport(content, fileName) {
|
||||
const fileToDownload = document.createElement('a');
|
||||
|
||||
fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content));
|
||||
fileToDownload.setAttribute('download', fileName);
|
||||
fileToDownload.style.display = 'none';
|
||||
document.body.appendChild(fileToDownload);
|
||||
fileToDownload.click();
|
||||
document.body.removeChild(fileToDownload);
|
||||
}
|
||||
|
||||
function listAccounts(state) {
|
||||
return async apiResponse => {
|
||||
const followings = apiResponse.data;
|
||||
let accounts = [];
|
||||
let next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
while (next) {
|
||||
apiResponse = await api(state).get(next.uri);
|
||||
next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
Array.prototype.push.apply(followings, apiResponse.data);
|
||||
}
|
||||
|
||||
accounts = followings.map(account => account.fqn);
|
||||
return [... new Set(accounts)];
|
||||
};
|
||||
}
|
||||
|
||||
export function exportFollows(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_REQUEST });
|
||||
const me = getState().get('me');
|
||||
return api(getState)
|
||||
.get(`/api/v1/accounts/${me}/following?limit=40`)
|
||||
.then(listAccounts(getState))
|
||||
.then((followings) => {
|
||||
followings = followings.map(fqn => fqn + ',true');
|
||||
followings.unshift('Account address,Show boosts');
|
||||
fileExport(followings.join('\n'), 'export_followings.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess)));
|
||||
dispatch({ type: EXPORT_FOLLOWS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exportBlocks(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/blocks?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((blocks) => {
|
||||
fileExport(blocks.join('\n'), 'export_block.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess)));
|
||||
dispatch({ type: EXPORT_BLOCKS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exportMutes(intl) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({ type: EXPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/mutes?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((mutes) => {
|
||||
fileExport(mutes.join('\n'), 'export_mutes.csv');
|
||||
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess)));
|
||||
dispatch({ type: EXPORT_MUTES_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import snackbar from './snackbar';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
export const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST';
|
||||
export const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS';
|
||||
export const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL';
|
||||
|
||||
export const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST';
|
||||
export const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS';
|
||||
export const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL';
|
||||
|
||||
export const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST';
|
||||
export const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS';
|
||||
export const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL';
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' },
|
||||
followersSuccess: { id: 'export_data.success.followers', defaultMessage: 'Followers exported successfully' },
|
||||
mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' },
|
||||
});
|
||||
|
||||
type ExportDataActions = {
|
||||
type: typeof EXPORT_FOLLOWS_REQUEST
|
||||
| typeof EXPORT_FOLLOWS_SUCCESS
|
||||
| typeof EXPORT_FOLLOWS_FAIL
|
||||
| typeof EXPORT_BLOCKS_REQUEST
|
||||
| typeof EXPORT_BLOCKS_SUCCESS
|
||||
| typeof EXPORT_BLOCKS_FAIL
|
||||
| typeof EXPORT_MUTES_REQUEST
|
||||
| typeof EXPORT_MUTES_SUCCESS
|
||||
| typeof EXPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
} | SnackbarAction
|
||||
|
||||
function fileExport(content: string, fileName: string) {
|
||||
const fileToDownload = document.createElement('a');
|
||||
|
||||
fileToDownload.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(content));
|
||||
fileToDownload.setAttribute('download', fileName);
|
||||
fileToDownload.style.display = 'none';
|
||||
document.body.appendChild(fileToDownload);
|
||||
fileToDownload.click();
|
||||
document.body.removeChild(fileToDownload);
|
||||
}
|
||||
|
||||
const listAccounts = (getState: () => RootState) => async(apiResponse: AxiosResponse<any, any>) => {
|
||||
const followings = apiResponse.data;
|
||||
let accounts = [];
|
||||
let next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
while (next) {
|
||||
apiResponse = await api(getState).get(next.uri);
|
||||
next = getLinks(apiResponse).refs.find(link => link.rel === 'next');
|
||||
Array.prototype.push.apply(followings, apiResponse.data);
|
||||
}
|
||||
|
||||
accounts = followings.map((account: { fqn: string }) => account.fqn);
|
||||
return Array.from(new Set(accounts));
|
||||
};
|
||||
|
||||
export const exportFollows = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_REQUEST });
|
||||
const me = getState().me;
|
||||
return api(getState)
|
||||
.get(`/api/v1/accounts/${me}/following?limit=40`)
|
||||
.then(listAccounts(getState))
|
||||
.then((followings) => {
|
||||
followings = followings.map(fqn => fqn + ',true');
|
||||
followings.unshift('Account address,Show boosts');
|
||||
fileExport(followings.join('\n'), 'export_followings.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
dispatch({ type: EXPORT_FOLLOWS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export const exportBlocks = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/blocks?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((blocks) => {
|
||||
fileExport(blocks.join('\n'), 'export_block.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
dispatch({ type: EXPORT_BLOCKS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
|
||||
export const exportMutes = () => (dispatch: React.Dispatch<ExportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: EXPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.get('/api/v1/mutes?limit=40')
|
||||
.then(listAccounts(getState))
|
||||
.then((mutes) => {
|
||||
fileExport(mutes.join('\n'), 'export_mutes.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
dispatch({ type: EXPORT_MUTES_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
|
@ -4,6 +4,9 @@ import snackbar from 'soapbox/actions/snackbar';
|
|||
|
||||
import api from '../api';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
||||
export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
|
||||
export const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL';
|
||||
|
@ -16,50 +19,61 @@ export const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST';
|
|||
export const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS';
|
||||
export const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL';
|
||||
|
||||
type ImportDataActions = {
|
||||
type: typeof IMPORT_FOLLOWS_REQUEST
|
||||
| typeof IMPORT_FOLLOWS_SUCCESS
|
||||
| typeof IMPORT_FOLLOWS_FAIL
|
||||
| typeof IMPORT_BLOCKS_REQUEST
|
||||
| typeof IMPORT_BLOCKS_SUCCESS
|
||||
| typeof IMPORT_BLOCKS_FAIL
|
||||
| typeof IMPORT_MUTES_REQUEST
|
||||
| typeof IMPORT_MUTES_SUCCESS
|
||||
| typeof IMPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
config?: string
|
||||
} | SnackbarAction
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'import_data.success.blocks', defaultMessage: 'Blocks imported successfully' },
|
||||
followersSuccess: { id: 'import_data.success.followers', defaultMessage: 'Followers imported successfully' },
|
||||
mutesSuccess: { id: 'import_data.success.mutes', defaultMessage: 'Mutes imported successfully' },
|
||||
});
|
||||
|
||||
export function importFollows(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importFollows = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/follow_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.followersSuccess)));
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function importBlocks(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importBlocks = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_BLOCKS_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/blocks_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.blocksSuccess)));
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_BLOCKS_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function importMutes(intl, params) {
|
||||
return (dispatch, getState) => {
|
||||
export const importMutes = (params: FormData) =>
|
||||
(dispatch: React.Dispatch<ImportDataActions>, getState: () => RootState) => {
|
||||
dispatch({ type: IMPORT_MUTES_REQUEST });
|
||||
return api(getState)
|
||||
.post('/api/pleroma/mutes_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mutesSuccess)));
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_MUTES_FAIL, error });
|
||||
});
|
||||
};
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
import { ALERT_SHOW } from './alerts';
|
||||
|
||||
export const show = (severity, message, actionLabel, actionLink) => ({
|
||||
type: ALERT_SHOW,
|
||||
message,
|
||||
actionLabel,
|
||||
actionLink,
|
||||
severity,
|
||||
});
|
||||
|
||||
export function info(message, actionLabel, actionLink) {
|
||||
return show('info', message, actionLabel, actionLink);
|
||||
}
|
||||
|
||||
export function success(message, actionLabel, actionLink) {
|
||||
return show('success', message, actionLabel, actionLink);
|
||||
}
|
||||
|
||||
export function error(message, actionLabel, actionLink) {
|
||||
return show('error', message, actionLabel, actionLink);
|
||||
}
|
||||
|
||||
export default {
|
||||
info,
|
||||
success,
|
||||
error,
|
||||
show,
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
import { ALERT_SHOW } from './alerts';
|
||||
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
|
||||
type SnackbarActionSeverity = 'info' | 'success' | 'error'
|
||||
|
||||
type SnackbarMessage = string | MessageDescriptor
|
||||
|
||||
export type SnackbarAction = {
|
||||
type: typeof ALERT_SHOW
|
||||
message: SnackbarMessage
|
||||
actionLabel?: string
|
||||
actionLink?: string
|
||||
severity: SnackbarActionSeverity
|
||||
}
|
||||
|
||||
export const show = (severity: SnackbarActionSeverity, message: SnackbarMessage, actionLabel?: string, actionLink?: string): SnackbarAction => ({
|
||||
type: ALERT_SHOW,
|
||||
message,
|
||||
actionLabel,
|
||||
actionLink,
|
||||
severity,
|
||||
});
|
||||
|
||||
export const info = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) =>
|
||||
show('info', message, actionLabel, actionLink);
|
||||
|
||||
export const success = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) =>
|
||||
show('success', message, actionLabel, actionLink);
|
||||
|
||||
export const error = (message: SnackbarMessage, actionLabel?: string, actionLink?: string) =>
|
||||
show('error', message, actionLabel, actionLink);
|
||||
|
||||
export default {
|
||||
info,
|
||||
success,
|
||||
error,
|
||||
show,
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import api from '../api';
|
||||
import api, { getLinks } from '../api';
|
||||
|
||||
import { fetchRelationships } from './accounts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
@ -32,11 +32,17 @@ export function fetchSuggestionsV1(params = {}) {
|
|||
|
||||
export function fetchSuggestionsV2(params = {}) {
|
||||
return (dispatch, getState) => {
|
||||
const next = getState().getIn(['suggestions', 'next']);
|
||||
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_REQUEST, skipLoading: true });
|
||||
return api(getState).get('/api/v2/suggestions', { params }).then(({ data: suggestions }) => {
|
||||
|
||||
return api(getState).get(next ? next.uri : '/api/v2/suggestions', next ? {} : { params }).then((response) => {
|
||||
const suggestions = response.data;
|
||||
const accounts = suggestions.map(({ account }) => account);
|
||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||
|
||||
dispatch(importFetchedAccounts(accounts));
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_SUCCESS, suggestions, skipLoading: true });
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_SUCCESS, suggestions, next, skipLoading: true });
|
||||
return suggestions;
|
||||
}).catch(error => {
|
||||
dispatch({ type: SUGGESTIONS_V2_FETCH_FAIL, error, skipLoading: true, skipAlert: true });
|
||||
|
|
|
@ -244,7 +244,9 @@ function checkEmailAvailability(email) {
|
|||
|
||||
return api(getState).get(`/api/v1/pepe/account/exists?email=${email}`, {
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
}).finally(() => dispatch({ type: SET_LOADING, value: false }));
|
||||
})
|
||||
.catch(() => {})
|
||||
.then(() => dispatch({ type: SET_LOADING, value: false }));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,8 @@ interface IScrollableList extends VirtuosoProps<any, any> {
|
|||
onRefresh?: () => Promise<any>,
|
||||
className?: string,
|
||||
itemClassName?: string,
|
||||
style?: React.CSSProperties,
|
||||
useWindowScroll?: boolean
|
||||
}
|
||||
|
||||
/** Legacy ScrollableList with Virtuoso for backwards-compatibility */
|
||||
|
@ -63,6 +65,8 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
placeholderCount = 0,
|
||||
initialTopMostItemIndex = 0,
|
||||
scrollerRef,
|
||||
style = {},
|
||||
useWindowScroll = true,
|
||||
}, ref) => {
|
||||
const settings = useSettings();
|
||||
const autoloadMore = settings.get('autoloadMore');
|
||||
|
@ -129,7 +133,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
const renderFeed = (): JSX.Element => (
|
||||
<Virtuoso
|
||||
ref={ref}
|
||||
useWindowScroll
|
||||
useWindowScroll={useWindowScroll}
|
||||
className={className}
|
||||
data={data}
|
||||
startReached={onScrollToTop}
|
||||
|
@ -137,6 +141,7 @@ const ScrollableList = React.forwardRef<VirtuosoHandle, IScrollableList>(({
|
|||
isScrolling={isScrolling => isScrolling && onScroll && onScroll()}
|
||||
itemContent={renderItem}
|
||||
initialTopMostItemIndex={showLoading ? 0 : initialTopMostItemIndex}
|
||||
style={style}
|
||||
context={{
|
||||
listClassName: className,
|
||||
itemClassName,
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { List as ImmutableList } from 'immutable';
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { FormattedList, FormattedMessage, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
|
@ -42,7 +43,7 @@ class StatusReplyMentions extends ImmutablePureComponent {
|
|||
return null;
|
||||
}
|
||||
|
||||
const to = status.get('mentions', []);
|
||||
const to = status.get('mentions', ImmutableList());
|
||||
|
||||
// The post is a reply, but it has no mentions.
|
||||
// Rare, but it can happen.
|
||||
|
@ -58,23 +59,27 @@ class StatusReplyMentions extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
// The typical case with a reply-to and a list of mentions.
|
||||
const accounts = to.slice(0, 2).map(account => (
|
||||
<HoverRefWrapper accountId={account.get('id')} inline>
|
||||
<Link to={`/@${account.get('acct')}`} className='reply-mentions__account'>@{account.get('username')}</Link>
|
||||
</HoverRefWrapper>
|
||||
)).toArray();
|
||||
|
||||
if (to.size > 2) {
|
||||
accounts.push(
|
||||
<span className='hover:underline cursor-pointer' role='presentation' onClick={this.handleOpenMentionsModal}>
|
||||
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: to.size - 2 }} />
|
||||
</span>,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='reply-mentions'>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
defaultMessage='Replying to {accounts}'
|
||||
values={{
|
||||
accounts: to.slice(0, 2).map((account) => (<>
|
||||
<HoverRefWrapper accountId={account.get('id')} inline>
|
||||
<Link to={`/@${account.get('acct')}`} className='reply-mentions__account'>@{account.get('username')}</Link>
|
||||
</HoverRefWrapper>
|
||||
{' '}
|
||||
</>)),
|
||||
more: to.size > 2 && (
|
||||
<span className='hover:underline cursor-pointer' role='presentation' onClick={this.handleOpenMentionsModal}>
|
||||
<FormattedMessage id='reply_mentions.more' defaultMessage='and {count} more' values={{ count: to.size - 2 }} />
|
||||
</span>
|
||||
),
|
||||
accounts: <FormattedList type='conjunction' value={accounts} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@ import { Text } from 'soapbox/components/ui';
|
|||
/** Represents a deleted item. */
|
||||
const Tombstone: React.FC = () => {
|
||||
return (
|
||||
<div className='my-4 p-9 flex items-center justify-center sm:rounded-xl bg-gray-100 border border-solid border-gray-200 dark:bg-slate-900 dark:border-slate-700'>
|
||||
<div className='p-9 flex items-center justify-center sm:rounded-xl bg-gray-100 border border-solid border-gray-200 dark:bg-slate-900 dark:border-slate-700'>
|
||||
<Text>
|
||||
<FormattedMessage id='statuses.tombstone' defaultMessage='One or more posts is unavailable.' />
|
||||
</Text>
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
|
||||
/**
|
||||
* iOS style loading spinner.
|
||||
* Adapted from: https://loading.io/css/
|
||||
* With some help scaling it: https://signalvnoise.com/posts/2577-loading-spinner-animation-using-css-and-webkit
|
||||
*/
|
||||
|
||||
.spinner {
|
||||
@apply inline-block relative w-20 h-20;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import Text from '../text/text';
|
|||
|
||||
import './spinner.css';
|
||||
|
||||
interface ILoadingIndicator {
|
||||
interface ISpinner {
|
||||
/** Width and height of the spinner in pixels. */
|
||||
size?: number,
|
||||
/** Whether to display "Loading..." beneath the spinner. */
|
||||
|
@ -14,7 +14,7 @@ interface ILoadingIndicator {
|
|||
}
|
||||
|
||||
/** Spinning loading placeholder. */
|
||||
const LoadingIndicator = ({ size = 30, withText = true }: ILoadingIndicator) => (
|
||||
const Spinner = ({ size = 30, withText = true }: ISpinner) => (
|
||||
<Stack space={2} justifyContent='center' alignItems='center'>
|
||||
<div className='spinner' style={{ width: size, height: size }}>
|
||||
{Array.from(Array(12).keys()).map(i => (
|
||||
|
@ -30,4 +30,4 @@ const LoadingIndicator = ({ size = 30, withText = true }: ILoadingIndicator) =>
|
|||
</Stack>
|
||||
);
|
||||
|
||||
export default LoadingIndicator;
|
||||
export default Spinner;
|
||||
|
|
|
@ -14,6 +14,7 @@ import { loadSoapboxConfig, getSoapboxConfig } from 'soapbox/actions/soapbox';
|
|||
import { fetchVerificationConfig } from 'soapbox/actions/verification';
|
||||
import * as BuildConfig from 'soapbox/build_config';
|
||||
import Helmet from 'soapbox/components/helmet';
|
||||
import { Spinner } from 'soapbox/components/ui';
|
||||
import AuthLayout from 'soapbox/features/auth_layout';
|
||||
import OnboardingWizard from 'soapbox/features/onboarding/onboarding-wizard';
|
||||
import PublicLayout from 'soapbox/features/public_layout';
|
||||
|
@ -115,10 +116,25 @@ const SoapboxMount = () => {
|
|||
return !(location.state?.soapboxModalKey && location.state?.soapboxModalKey !== prevRouterProps?.location?.state?.soapboxModalKey);
|
||||
};
|
||||
|
||||
if (me === null) return null;
|
||||
if (me && !account) return null;
|
||||
if (!isLoaded) return null;
|
||||
if (localeLoading) return null;
|
||||
/** Whether to display a loading indicator. */
|
||||
const showLoading = [
|
||||
me === null,
|
||||
me && !account,
|
||||
!isLoaded,
|
||||
localeLoading,
|
||||
].some(Boolean);
|
||||
|
||||
if (showLoading) {
|
||||
return (
|
||||
<div className='p-4 h-screen w-screen flex items-center justify-center'>
|
||||
<Helmet>
|
||||
{themeCss && <style id='theme' type='text/css'>{`:root{${themeCss}}`}</style>}
|
||||
</Helmet>
|
||||
|
||||
<Spinner size={40} withText={false} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const waitlisted = account && !account.source.get('approved', true);
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ const messages = defineMessages({
|
|||
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
|
||||
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
|
||||
removeFromFollowers: { id: 'account.remove_from_followers', defaultMessage: 'Remove this follower' },
|
||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||
add_or_remove_from_list: { id: 'account.add_or_remove_from_list', defaultMessage: 'Add or Remove from lists' },
|
||||
deactivateUser: { id: 'admin.users.actions.deactivate_user', defaultMessage: 'Deactivate @{name}' },
|
||||
|
@ -283,6 +284,14 @@ class Header extends ImmutablePureComponent {
|
|||
});
|
||||
}
|
||||
|
||||
if (features.removeFromFollowers && account.getIn(['relationship', 'followed_by'])) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.removeFromFollowers),
|
||||
action: this.props.onRemoveFromFollowers,
|
||||
icon: require('@tabler/icons/icons/user-x.svg'),
|
||||
});
|
||||
}
|
||||
|
||||
if (account.getIn(['relationship', 'muting'])) {
|
||||
menu.push({
|
||||
text: intl.formatMessage(messages.unmute, { name: account.get('username') }),
|
||||
|
|
|
@ -25,6 +25,7 @@ class Header extends ImmutablePureComponent {
|
|||
onUnblockDomain: PropTypes.func.isRequired,
|
||||
onEndorseToggle: PropTypes.func.isRequired,
|
||||
onAddToList: PropTypes.func.isRequired,
|
||||
onRemoveFromFollowers: PropTypes.func.isRequired,
|
||||
username: PropTypes.string,
|
||||
history: PropTypes.object,
|
||||
};
|
||||
|
@ -141,6 +142,10 @@ class Header extends ImmutablePureComponent {
|
|||
this.props.onShowNote(this.props.account);
|
||||
}
|
||||
|
||||
handleRemoveFromFollowers = () => {
|
||||
this.props.onRemoveFromFollowers(this.props.account);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account } = this.props;
|
||||
const moved = (account) ? account.get('moved') : false;
|
||||
|
@ -177,6 +182,7 @@ class Header extends ImmutablePureComponent {
|
|||
onSuggestUser={this.handleSuggestUser}
|
||||
onUnsuggestUser={this.handleUnsuggestUser}
|
||||
onShowNote={this.handleShowNote}
|
||||
onRemoveFromFollowers={this.handleRemoveFromFollowers}
|
||||
username={this.props.username}
|
||||
/>
|
||||
</>
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
unpinAccount,
|
||||
subscribeAccount,
|
||||
unsubscribeAccount,
|
||||
removeFromFollowers,
|
||||
} from 'soapbox/actions/accounts';
|
||||
import {
|
||||
verifyUser,
|
||||
|
@ -56,6 +57,7 @@ const messages = defineMessages({
|
|||
demotedToUser: { id: 'admin.users.actions.demote_to_user_message', defaultMessage: '@{acct} was demoted to a regular user' },
|
||||
userSuggested: { id: 'admin.users.user_suggested_message', defaultMessage: '@{acct} was suggested' },
|
||||
userUnsuggested: { id: 'admin.users.user_unsuggested_message', defaultMessage: '@{acct} was unsuggested' },
|
||||
removeFromFollowersConfirm: { id: 'confirmations.remove_from_followers.confirm', defaultMessage: 'Remove' },
|
||||
});
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
|
@ -269,6 +271,21 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
onShowNote(account) {
|
||||
dispatch(initAccountNoteModal(account));
|
||||
},
|
||||
|
||||
onRemoveFromFollowers(account) {
|
||||
dispatch((_, getState) => {
|
||||
const unfollowModal = getSettings(getState()).get('unfollowModal');
|
||||
if (unfollowModal) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: <FormattedMessage id='confirmations.remove_from_followers.message' defaultMessage='Are you sure you want to remove {name} from your followers?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||
confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
|
||||
onConfirm: () => dispatch(removeFromFollowers(account.get('id'))),
|
||||
}));
|
||||
} else {
|
||||
dispatch(removeFromFollowers(account.get('id')));
|
||||
}
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
|
||||
|
|
|
@ -40,23 +40,23 @@ const Search: React.FC = () => {
|
|||
const hasValue = value.length > 0;
|
||||
|
||||
return (
|
||||
<div className='aliases_search search'>
|
||||
<label>
|
||||
<div className='flex items-center gap-1'>
|
||||
<label className='flex-grow relative'>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
|
||||
|
||||
<input
|
||||
className='search__input'
|
||||
className='block w-full sm:text-sm dark:bg-slate-800 dark:text-white dark:placeholder:text-gray-500 focus:ring-indigo-500 focus:border-indigo-500 rounded-full'
|
||||
type='text'
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
onKeyUp={handleKeyUp}
|
||||
placeholder={intl.formatMessage(messages.search)}
|
||||
/>
|
||||
</label>
|
||||
|
||||
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
|
||||
<Icon src={require('@tabler/icons/icons/backspace.svg')} aria-label={intl.formatMessage(messages.search)} className={classNames('svg-icon--backspace', { active: hasValue })} />
|
||||
</div>
|
||||
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
|
||||
<Icon src={require('@tabler/icons/icons/backspace.svg')} aria-label={intl.formatMessage(messages.search)} className={classNames('svg-icon--backspace', { active: hasValue })} />
|
||||
</div>
|
||||
</label>
|
||||
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { FormattedList, FormattedMessage } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
|
@ -47,14 +47,23 @@ const ReplyMentions: React.FC = () => {
|
|||
);
|
||||
}
|
||||
|
||||
const accounts = to.slice(0, 2).map((acct: string) => (
|
||||
<span className='reply-mentions__account'>@{acct.split('@')[0]}</span>
|
||||
)).toArray();
|
||||
|
||||
if (to.size > 2) {
|
||||
accounts.push(
|
||||
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: to.size - 2 }} />,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a href='#' className='reply-mentions' onClick={handleClick}>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
defaultMessage='Replying to {accounts}'
|
||||
values={{
|
||||
accounts: to.slice(0, 2).map((acct: string) => <><span className='reply-mentions__account'>@{acct.split('@')[0]}</span>{' '}</>),
|
||||
more: to.size > 2 && <FormattedMessage id='reply_mentions.more' defaultMessage='and {count} more' values={{ count: to.size - 2 }} />,
|
||||
accounts: <FormattedList type='conjunction' value={accounts} />,
|
||||
}}
|
||||
/>
|
||||
</a>
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Button, Form, FormActions, Text } from 'soapbox/components/ui';
|
||||
|
||||
export default @connect()
|
||||
@injectIntl
|
||||
class CSVExporter extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
action: PropTypes.func.isRequired,
|
||||
messages: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
isLoading: false,
|
||||
}
|
||||
|
||||
handleClick = (event) => {
|
||||
const { dispatch, action, intl } = this.props;
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
dispatch(action(intl)).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
}).catch((error) => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, messages } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form>
|
||||
<Text size='xl' weight='bold'>{intl.formatMessage(messages.input_label)}</Text>
|
||||
<Text theme='muted'>{intl.formatMessage(messages.input_hint)}</Text>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='primary' onClick={this.handleClick}>
|
||||
{intl.formatMessage(messages.submit)}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { MessageDescriptor, useIntl } from 'react-intl';
|
||||
|
||||
import { Button, Form, FormActions, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { AppDispatch } from 'soapbox/store';
|
||||
|
||||
interface ICSVExporter {
|
||||
messages: {
|
||||
input_label: MessageDescriptor,
|
||||
input_hint: MessageDescriptor,
|
||||
submit: MessageDescriptor,
|
||||
},
|
||||
action: () => (dispatch: AppDispatch, getState: any) => Promise<void>,
|
||||
}
|
||||
|
||||
const CSVExporter: React.FC<ICSVExporter> = ({ messages, action }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleClick: React.MouseEventHandler = (event) => {
|
||||
setIsLoading(true);
|
||||
dispatch(action()).then(() => {
|
||||
setIsLoading(false);
|
||||
}).catch(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Form>
|
||||
<Text size='xl' weight='bold'>{intl.formatMessage(messages.input_label)}</Text>
|
||||
<Text theme='muted'>{intl.formatMessage(messages.input_hint)}</Text>
|
||||
|
||||
<FormActions>
|
||||
<Button theme='primary' onClick={handleClick} disabled={isLoading}>
|
||||
{intl.formatMessage(messages.submit)}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default CSVExporter;
|
|
@ -1,15 +1,11 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
exportFollows,
|
||||
exportBlocks,
|
||||
exportMutes,
|
||||
} from 'soapbox/actions/export_data';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import Column from '../ui/components/column';
|
||||
|
||||
|
@ -38,29 +34,16 @@ const muteMessages = defineMessages({
|
|||
submit: { id: 'export_data.actions.export_mutes', defaultMessage: 'Export mutes' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
features: getFeatures(state.get('instance')),
|
||||
});
|
||||
const ExportData = () => {
|
||||
const intl = useIntl();
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class ExportData extends ImmutablePureComponent {
|
||||
return (
|
||||
<Column icon='cloud-download-alt' label={intl.formatMessage(messages.heading)}>
|
||||
<CSVExporter action={exportFollows} messages={followMessages} />
|
||||
<CSVExporter action={exportBlocks} messages={blockMessages} />
|
||||
<CSVExporter action={exportMutes} messages={muteMessages} />
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
features: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<Column icon='cloud-download-alt' label={intl.formatMessage(messages.heading)}>
|
||||
<CSVExporter action={exportFollows} messages={followMessages} />
|
||||
<CSVExporter action={exportBlocks} messages={blockMessages} />
|
||||
<CSVExporter action={exportMutes} messages={muteMessages} />
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
export default ExportData;
|
|
@ -1,71 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Button, Form, FormActions, Text } from 'soapbox/components/ui';
|
||||
|
||||
export default @connect()
|
||||
@injectIntl
|
||||
class CSVImporter extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
action: PropTypes.func.isRequired,
|
||||
messages: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
state = {
|
||||
file: null,
|
||||
isLoading: false,
|
||||
}
|
||||
|
||||
handleSubmit = (event) => {
|
||||
const { dispatch, action, intl } = this.props;
|
||||
|
||||
const params = new FormData();
|
||||
params.append('list', this.state.file);
|
||||
|
||||
this.setState({ isLoading: true });
|
||||
dispatch(action(intl, params)).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
}).catch((error) => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
handleFileChange = e => {
|
||||
const [file] = e.target.files || [];
|
||||
this.setState({ file });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, messages } = this.props;
|
||||
|
||||
return (
|
||||
<Form onSubmit={this.handleSubmit} disabled={this.state.isLoading}>
|
||||
<Text size='xl' weight='bold' tag='label'>{intl.formatMessage(messages.input_label)}</Text>
|
||||
<div>
|
||||
<input
|
||||
className='text-black dark:text-white'
|
||||
type='file'
|
||||
accept={['.csv', 'text/csv']}
|
||||
onChange={this.handleFileChange}
|
||||
required
|
||||
/>
|
||||
<Text theme='muted'>{intl.formatMessage(messages.input_hint)}</Text>
|
||||
</div>
|
||||
<FormActions>
|
||||
<Button type='submit' theme='primary' disabled={this.state.isLoading}>
|
||||
{intl.formatMessage(messages.submit)}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
import React from 'react';
|
||||
import { useState } from 'react';
|
||||
import { MessageDescriptor, useIntl } from 'react-intl';
|
||||
|
||||
import { Button, FileInput, Form, FormActions, FormGroup, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import { AppDispatch } from 'soapbox/store';
|
||||
|
||||
interface ICSVImporter {
|
||||
messages: {
|
||||
input_label: MessageDescriptor,
|
||||
input_hint: MessageDescriptor,
|
||||
submit: MessageDescriptor,
|
||||
},
|
||||
action: (params: FormData) => (dispatch: AppDispatch, getState: any) => Promise<void>,
|
||||
}
|
||||
|
||||
const CSVImporter: React.FC<ICSVImporter> = ({ messages, action }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const intl = useIntl();
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [file, setFile] = useState<File | null | undefined>(null);
|
||||
|
||||
|
||||
const handleSubmit: React.FormEventHandler = (event) => {
|
||||
const params = new FormData();
|
||||
params.append('list', file!);
|
||||
|
||||
setIsLoading(true);
|
||||
dispatch(action(params)).then(() => {
|
||||
setIsLoading(false);
|
||||
}).catch(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const handleFileChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||
const file = e.target.files?.item(0);
|
||||
setFile(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<Text size='xl' weight='bold' tag='label'>{intl.formatMessage(messages.input_label)}</Text>
|
||||
<FormGroup
|
||||
hintText={<Text theme='muted'>{intl.formatMessage(messages.input_hint)}</Text>}
|
||||
>
|
||||
<FileInput
|
||||
accept='.csv,text/csv'
|
||||
onChange={handleFileChange}
|
||||
required
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormActions>
|
||||
<Button type='submit' theme='primary' disabled={isLoading}>
|
||||
{intl.formatMessage(messages.submit)}
|
||||
</Button>
|
||||
</FormActions>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
||||
export default CSVImporter;
|
|
@ -1,14 +1,12 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { connect } from 'react-redux';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import {
|
||||
importFollows,
|
||||
importBlocks,
|
||||
importMutes,
|
||||
} from 'soapbox/actions/import_data';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import Column from '../ui/components/column';
|
||||
|
@ -38,29 +36,17 @@ const muteMessages = defineMessages({
|
|||
submit: { id: 'import_data.actions.import_mutes', defaultMessage: 'Import mutes' },
|
||||
});
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
features: getFeatures(state.get('instance')),
|
||||
});
|
||||
const ImportData = () => {
|
||||
const intl = useIntl();
|
||||
const features = getFeatures(useAppSelector((state) => state.instance));
|
||||
|
||||
export default @connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class ImportData extends ImmutablePureComponent {
|
||||
return (
|
||||
<Column icon='cloud-upload-alt' label={intl.formatMessage(messages.heading)}>
|
||||
<CSVImporter action={importFollows} messages={followMessages} />
|
||||
<CSVImporter action={importBlocks} messages={blockMessages} />
|
||||
{features.importMutes && <CSVImporter action={importMutes} messages={muteMessages} />}
|
||||
</Column>
|
||||
);
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
features: PropTypes.object,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { intl, features } = this.props;
|
||||
|
||||
return (
|
||||
<Column icon='cloud-upload-alt' label={intl.formatMessage(messages.heading)}>
|
||||
<CSVImporter action={importFollows} messages={followMessages} />
|
||||
<CSVImporter action={importBlocks} messages={blockMessages} />
|
||||
{features.importMutes && <CSVImporter action={importMutes} messages={muteMessages} />}
|
||||
</Column>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
export default ImportData;
|
|
@ -1,8 +1,10 @@
|
|||
import { Map as ImmutableMap } from 'immutable';
|
||||
import debounce from 'lodash/debounce';
|
||||
import * as React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import ScrollableList from 'soapbox/components/scrollable_list';
|
||||
import { Button, Card, CardBody, Stack, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account_container';
|
||||
import { useAppSelector } from 'soapbox/hooks';
|
||||
|
@ -13,24 +15,42 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
|||
const dispatch = useDispatch();
|
||||
|
||||
const suggestions = useAppSelector((state) => state.suggestions.get('items'));
|
||||
const suggestionsToRender = suggestions.slice(0, 5);
|
||||
const hasMore = useAppSelector((state) => !!state.suggestions.get('next'));
|
||||
const isLoading = useAppSelector((state) => state.suggestions.get('isLoading'));
|
||||
|
||||
const handleLoadMore = debounce(() => {
|
||||
if (isLoading) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return dispatch(fetchSuggestions());
|
||||
}, 300);
|
||||
|
||||
React.useEffect(() => {
|
||||
dispatch(fetchSuggestions());
|
||||
dispatch(fetchSuggestions({ limit: 20 }));
|
||||
}, []);
|
||||
|
||||
const renderSuggestions = () => {
|
||||
return (
|
||||
<div className='sm:pt-4 sm:pb-10 flex flex-col divide-y divide-solid divide-gray-200 dark:divide-slate-700'>
|
||||
{suggestionsToRender.map((suggestion: ImmutableMap<string, any>) => (
|
||||
<div key={suggestion.get('account')} className='py-2'>
|
||||
<AccountContainer
|
||||
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
||||
id={suggestion.get('account')}
|
||||
showProfileHoverCard={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className='sm:pt-4 sm:pb-10 flex flex-col'>
|
||||
<ScrollableList
|
||||
isLoading={isLoading}
|
||||
scrollKey='suggestions'
|
||||
onLoadMore={handleLoadMore}
|
||||
hasMore={hasMore}
|
||||
useWindowScroll={false}
|
||||
style={{ height: 320 }}
|
||||
>
|
||||
{suggestions.map((suggestion: ImmutableMap<string, any>) => (
|
||||
<div key={suggestion.get('account')} className='py-2'>
|
||||
<AccountContainer
|
||||
// @ts-ignore: TS thinks `id` is passed to <Account>, but it isn't
|
||||
id={suggestion.get('account')}
|
||||
showProfileHoverCard={false}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</ScrollableList>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -46,7 +66,7 @@ const SuggestedAccountsStep = ({ onNext }: { onNext: () => void }) => {
|
|||
};
|
||||
|
||||
const renderBody = () => {
|
||||
if (suggestionsToRender.isEmpty()) {
|
||||
if (suggestions.isEmpty()) {
|
||||
return renderEmpty();
|
||||
} else {
|
||||
return renderSuggestions();
|
||||
|
|
|
@ -2,7 +2,7 @@ import classNames from 'classnames';
|
|||
import { History } from 'history';
|
||||
import React from 'react';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage, IntlShape } from 'react-intl';
|
||||
import { defineMessages, injectIntl, FormattedMessage, IntlShape, FormattedList } from 'react-intl';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
|
||||
import AttachmentThumbs from 'soapbox/components/attachment-thumbs';
|
||||
|
@ -67,10 +67,9 @@ class QuotedStatus extends ImmutablePureComponent<IQuotedStatus> {
|
|||
<div className='reply-mentions'>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
defaultMessage='Replying to {accounts}'
|
||||
values={{
|
||||
accounts: `@${account.username}`,
|
||||
more: false,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
@ -84,14 +83,21 @@ class QuotedStatus extends ImmutablePureComponent<IQuotedStatus> {
|
|||
}
|
||||
}
|
||||
|
||||
const accounts = to.slice(0, 2).map(account => <>@{account.username}</>).toArray();
|
||||
|
||||
if (to.size > 2) {
|
||||
accounts.push(
|
||||
<FormattedMessage id='reply_mentions.more' defaultMessage='{count} more' values={{ count: to.size - 2 }} />,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='reply-mentions'>
|
||||
<FormattedMessage
|
||||
id='reply_mentions.reply'
|
||||
defaultMessage='Replying to {accounts}{more}'
|
||||
defaultMessage='Replying to {accounts}'
|
||||
values={{
|
||||
accounts: to.slice(0, 2).map(account => `@${account.username} `),
|
||||
more: to.size > 2 && <FormattedMessage id='reply_mentions.more' defaultMessage='and {count} more' values={{ count: to.size - 2 }} />,
|
||||
accounts: <FormattedList type='conjunction' value={accounts} />,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -561,7 +561,7 @@ class Status extends ImmutablePureComponent<IStatus, IStatusState> {
|
|||
|
||||
renderTombstone(id: string) {
|
||||
return (
|
||||
<div className='pb-4'>
|
||||
<div className='py-4 pb-8'>
|
||||
<Tombstone key={id} />
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import SiteLogo from 'soapbox/components/site-logo';
|
||||
import { Button, Icon, Modal } from 'soapbox/components/ui';
|
||||
import { Text, Button, Icon, Modal } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useFeatures, useSoapboxConfig } from 'soapbox/hooks';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -17,6 +17,7 @@ interface ILandingPageModal {
|
|||
onClose: (type: string) => void,
|
||||
}
|
||||
|
||||
/** Login and links to display from the hamburger menu of the homepage. */
|
||||
const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
|
@ -41,13 +42,13 @@ const LandingPageModal: React.FC<ILandingPageModal> = ({ onClose }) => {
|
|||
<a
|
||||
href={links.get('help')}
|
||||
target='_blank'
|
||||
className='p-3 flex items-center rounded-md dark:hover:bg-slate-900/50 hover:bg-gray-50'
|
||||
className='p-3 space-x-3 flex items-center rounded-md dark:hover:bg-slate-900/50 hover:bg-gray-50'
|
||||
>
|
||||
<Icon src={require('@tabler/icons/icons/lifebuoy.svg')} className='flex-shrink-0 h-6 w-6 text-gray-400 dark:text-gray-200' />
|
||||
|
||||
<span className='ml-3 text-base font-medium text-gray-900 dark:text-gray-200'>
|
||||
<Text weight='medium'>
|
||||
{intl.formatMessage(messages.helpCenter)}
|
||||
</span>
|
||||
</Text>
|
||||
</a>
|
||||
</nav>
|
||||
)}
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "إلغاء",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Encaboxar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Отказ",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "বাতিল করতে",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel·lar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Bloquejar {target}",
|
||||
"report.block_hint": "També vols bloquejar aquest compte?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Annullà",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Zrušit",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Zablokovat {target}",
|
||||
"report.block_hint": "Chcete zablokovat tento účet?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Canslo",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Annuller",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,8 @@
|
|||
"reply_indicator.cancel": "Abbrechen",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "und {count, plural, one {einen weiteren Nutzer} other {# weitere Nutzer}}",
|
||||
"reply_mentions.reply": "Antwort an {accounts}{more}",
|
||||
"reply_mentions.more": "{count, plural, one {einen weiteren Nutzer} other {# weitere Nutzer}}",
|
||||
"reply_mentions.reply": "Antwort an {accounts}",
|
||||
"reply_mentions.reply_empty": "Antwort auf einen Beitrag",
|
||||
"report.block": "{target} blockieren.",
|
||||
"report.block_hint": "Soll dieses Konto zusammen mit der Meldung auch gleich blockiert werden?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Άκυρο",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,8 @@
|
|||
"reply_indicator.cancel": "𐑒𐑨𐑯𐑕𐑩𐑤",
|
||||
"reply_mentions.account.add": "𐑨𐑛 𐑑 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
||||
"reply_mentions.account.remove": "𐑮𐑦𐑥𐑵𐑝 𐑓𐑮𐑪𐑥 𐑥𐑧𐑯𐑖𐑩𐑯𐑟",
|
||||
"reply_mentions.more": "𐑯 {count} 𐑥𐑹",
|
||||
"reply_mentions.reply": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 {accounts}{more}",
|
||||
"reply_mentions.more": "{count} 𐑥𐑹",
|
||||
"reply_mentions.reply": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 {accounts}",
|
||||
"reply_mentions.reply_empty": "𐑮𐑦𐑐𐑤𐑲𐑦𐑙 𐑑 𐑐𐑴𐑕𐑑",
|
||||
"report.block": "𐑚𐑤𐑪𐑒 {target}",
|
||||
"report.block_hint": "𐑛𐑵 𐑿 𐑷𐑤𐑕𐑴 𐑢𐑪𐑯𐑑 𐑑 𐑚𐑤𐑪𐑒 𐑞𐑦𐑕 𐑩𐑒𐑬𐑯𐑑?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Nuligi",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Tühista",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Utzi",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "لغو",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Peruuta",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Annuler",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,8 @@
|
|||
"reply_indicator.cancel": "ביטול",
|
||||
"reply_mentions.account.add": "הוסף לאזכורים",
|
||||
"reply_mentions.account.remove": "הסר מהאזכורים",
|
||||
"reply_mentions.more": "ו-{count} עוד",
|
||||
"reply_mentions.reply": "משיב ל-{accounts}{more}",
|
||||
"reply_mentions.more": "{count} עוד",
|
||||
"reply_mentions.reply": "משיב ל-{accounts}",
|
||||
"reply_mentions.reply_empty": "משיב לפוסט",
|
||||
"report.block": "חסום {target}",
|
||||
"report.block_hint": "האם גם אתה רוצה לחסום את החשבון הזה?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Otkaži",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Mégsem",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Չեղարկել",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Batal",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Nihiligar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -788,8 +788,8 @@
|
|||
"reply_indicator.cancel": "Hætta við",
|
||||
"reply_mentions.account.add": "Bæta við í tilvísanirnar",
|
||||
"reply_mentions.account.remove": "Fjarlægja úr tilvísunum",
|
||||
"reply_mentions.more": "og {count} fleirum",
|
||||
"reply_mentions.reply": "Að svara {accounts}{more}",
|
||||
"reply_mentions.more": "{count} fleirum",
|
||||
"reply_mentions.reply": "Að svara {accounts}",
|
||||
"reply_mentions.reply_empty": "Að svara færslu",
|
||||
"report.block": "Loka á {target}",
|
||||
"report.block_hint": "Viltu líka loka á þennan reikning?",
|
||||
|
|
|
@ -859,8 +859,8 @@
|
|||
"reply_indicator.cancel": "Annulla",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "e ancora {count}",
|
||||
"reply_mentions.reply": "Risponde a {accounts}{more}",
|
||||
"reply_mentions.more": "ancora {count}",
|
||||
"reply_mentions.reply": "Risponde a {accounts}",
|
||||
"reply_mentions.reply_empty": "Rispondendo al contenuto",
|
||||
"report.block": "Blocca {target}",
|
||||
"report.block_hint": "Vuoi anche bloccare questa persona?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "キャンセル",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "{target}さんをブロック",
|
||||
"report.block_hint": "このアカウントをブロックしますか?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "უარყოფა",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Қайтып алу",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "취소",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Annuleren",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancel",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Avbryt",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Anullar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -910,8 +910,8 @@
|
|||
"reply_indicator.cancel": "Anuluj",
|
||||
"reply_mentions.account.add": "Dodaj do wspomnianych",
|
||||
"reply_mentions.account.remove": "Usuń z wspomnianych",
|
||||
"reply_mentions.more": "i {count} więcej",
|
||||
"reply_mentions.reply": "W odpowiedzi do {accounts}{more}",
|
||||
"reply_mentions.more": "{count} więcej",
|
||||
"reply_mentions.reply": "W odpowiedzi do {accounts}",
|
||||
"reply_mentions.reply_empty": "W odpowiedzi na wpis",
|
||||
"report.block": "Zablokuj {target}",
|
||||
"report.block_hint": "Czy chcesz też zablokować to konto?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Cancelar",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Bloquear {target}",
|
||||
"report.block_hint": "Desejas também bloquear esta conta?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Anulează",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Отмена",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Zrušiť",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Prekliči",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Anuloje",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Poništi",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Поништи",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Ångra",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "எதிராணை",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "రద్దు చెయ్యి",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "ยกเลิก",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "İptal",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "Відмінити",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "取消",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "屏蔽帐号 {target}",
|
||||
"report.block_hint": "你是否也要屏蔽这个帐号呢?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "取消",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -859,8 +859,6 @@
|
|||
"reply_indicator.cancel": "取消",
|
||||
"reply_mentions.account.add": "Add to mentions",
|
||||
"reply_mentions.account.remove": "Remove from mentions",
|
||||
"reply_mentions.more": "and {count} more",
|
||||
"reply_mentions.reply": "Replying to {accounts}{more}",
|
||||
"reply_mentions.reply_empty": "Replying to post",
|
||||
"report.block": "Block {target}",
|
||||
"report.block_hint": "Do you also want to block this account?",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import { SUGGESTIONS_DISMISS } from 'soapbox/actions/suggestions';
|
||||
|
||||
|
@ -7,7 +7,8 @@ import reducer from '../suggestions';
|
|||
describe('suggestions reducer', () => {
|
||||
it('should return the initial state', () => {
|
||||
expect(reducer(undefined, {})).toEqual(ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
items: ImmutableOrderedSet(),
|
||||
next: null,
|
||||
isLoading: false,
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -19,6 +19,7 @@ import {
|
|||
ACCOUNT_UNSUBSCRIBE_SUCCESS,
|
||||
ACCOUNT_PIN_SUCCESS,
|
||||
ACCOUNT_UNPIN_SUCCESS,
|
||||
ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
|
||||
RELATIONSHIPS_FETCH_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
|
@ -108,6 +109,7 @@ export default function relationships(state = initialState, action) {
|
|||
case ACCOUNT_PIN_SUCCESS:
|
||||
case ACCOUNT_UNPIN_SUCCESS:
|
||||
case ACCOUNT_NOTE_SUBMIT_SUCCESS:
|
||||
case ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS:
|
||||
return normalizeRelationship(state, action.relationship);
|
||||
case RELATIONSHIPS_FETCH_SUCCESS:
|
||||
return normalizeRelationships(state, action.relationships);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, fromJS } from 'immutable';
|
||||
|
||||
import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS } from 'soapbox/actions/accounts';
|
||||
import { DOMAIN_BLOCK_SUCCESS } from 'soapbox/actions/domain_blocks';
|
||||
|
@ -14,7 +14,8 @@ import {
|
|||
} from '../actions/suggestions';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
items: ImmutableOrderedSet(),
|
||||
next: null,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
|
@ -33,10 +34,11 @@ const importAccounts = (state, accounts) => {
|
|||
});
|
||||
};
|
||||
|
||||
const importSuggestions = (state, suggestions) => {
|
||||
const importSuggestions = (state, suggestions, next) => {
|
||||
return state.withMutations(state => {
|
||||
state.set('items', fromJS(suggestions.map(x => ({ ...x, account: x.account.id }))));
|
||||
state.update('items', items => items.concat(fromJS(suggestions.map(x => ({ ...x, account: x.account.id })))));
|
||||
state.set('isLoading', false);
|
||||
state.set('next', next);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -56,7 +58,7 @@ export default function suggestionsReducer(state = initialState, action) {
|
|||
case SUGGESTIONS_FETCH_SUCCESS:
|
||||
return importAccounts(state, action.accounts);
|
||||
case SUGGESTIONS_V2_FETCH_SUCCESS:
|
||||
return importSuggestions(state, action.suggestions);
|
||||
return importSuggestions(state, action.suggestions, action.next);
|
||||
case SUGGESTIONS_FETCH_FAIL:
|
||||
case SUGGESTIONS_V2_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
|
|
|
@ -426,6 +426,15 @@ const getInstanceFeatures = (instance: Instance) => {
|
|||
*/
|
||||
remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'),
|
||||
|
||||
/**
|
||||
* Ability to remove an account from your followers.
|
||||
* @see POST /api/v1/accounts/:id/remove_from_followers
|
||||
*/
|
||||
removeFromFollowers: any([
|
||||
v.software === MASTODON && gte(v.compatVersion, '3.5.0'),
|
||||
v.software === PLEROMA && v.build === SOAPBOX && gte(v.version, '2.4.50'),
|
||||
]),
|
||||
|
||||
reportMultipleStatuses: any([
|
||||
v.software === MASTODON,
|
||||
v.software === PLEROMA,
|
||||
|
|
Loading…
Reference in New Issue