Merge remote-tracking branch 'origin/develop' into just-messing-around

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
strip-front-mentions
marcin mikołajczak 2022-01-03 18:52:22 +01:00
commit 11107c6df9
39 zmienionych plików z 700 dodań i 293 usunięć

Wyświetl plik

@ -148,8 +148,16 @@ export function fetchAccountByUsername(username) {
const instance = state.get('instance');
const features = getFeatures(instance);
const me = state.get('me');
if (features.accountByUsername) {
if (!me && features.accountLookup) {
dispatch(accountLookup(username)).then(account => {
dispatch(fetchAccountSuccess(account));
}).catch(error => {
dispatch(fetchAccountFail(null, error));
dispatch(importErrorWhileFetchingAccountByUsername(username));
});
} else if (features.accountByUsername) {
api(getState).get(`/api/v1/accounts/${username}`).then(response => {
dispatch(fetchRelationships([response.data.id]));
dispatch(importFetchedAccount(response.data));

Wyświetl plik

@ -48,6 +48,10 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST';
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS';
export const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL';
export const REMOTE_INTERACTION_REQUEST = 'REMOTE_INTERACTION_REQUEST';
export const REMOTE_INTERACTION_SUCCESS = 'REMOTE_INTERACTION_SUCCESS';
export const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL';
const messages = defineMessages({
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
@ -475,3 +479,46 @@ export function unpinFail(status, error) {
skipLoading: true,
};
}
export function remoteInteraction(ap_id, profile) {
return (dispatch, getState) => {
dispatch(remoteInteractionRequest(ap_id, profile));
return api(getState).post('/api/v1/pleroma/remote_interaction', { ap_id, profile }).then(({ data }) => {
if (data.error) throw new Error(data.error);
dispatch(remoteInteractionSuccess(ap_id, profile, data.url));
return data.url;
}).catch(error => {
dispatch(remoteInteractionFail(ap_id, profile, error));
throw error;
});
};
}
export function remoteInteractionRequest(ap_id, profile) {
return {
type: REMOTE_INTERACTION_REQUEST,
ap_id,
profile,
};
}
export function remoteInteractionSuccess(ap_id, profile, url) {
return {
type: REMOTE_INTERACTION_SUCCESS,
ap_id,
profile,
url,
};
}
export function remoteInteractionFail(ap_id, profile, error) {
return {
type: REMOTE_INTERACTION_FAIL,
ap_id,
profile,
error,
};
}

Wyświetl plik

@ -34,6 +34,7 @@ class Poll extends ImmutablePureComponent {
dispatch: PropTypes.func,
disabled: PropTypes.bool,
me: SoapboxPropTypes.me,
status: PropTypes.string,
};
state = {
@ -81,7 +82,11 @@ class Poll extends ImmutablePureComponent {
};
openUnauthorizedModal = () => {
this.props.dispatch(openModal('UNAUTHORIZED'));
const { dispatch, status } = this.props;
dispatch(openModal('UNAUTHORIZED', {
action: 'POLL_VOTE',
ap_id: status,
}));
}
handleRefresh = () => {

Wyświetl plik

@ -52,6 +52,7 @@ export const ProfileHoverCard = ({ visible }) => {
const [popperElement, setPopperElement] = useState(null);
const me = useSelector(state => state.get('me'));
const accountId = useSelector(state => state.getIn(['profile_hover_card', 'accountId']));
const account = useSelector(state => accountId && getAccount(state, accountId));
const targetRef = useSelector(state => state.getIn(['profile_hover_card', 'ref', 'current']));
@ -65,7 +66,7 @@ export const ProfileHoverCard = ({ visible }) => {
if (!account) return null;
const accountBio = { __html: account.get('note_emojified') };
const followedBy = account.getIn(['relationship', 'followed_by']);
const followedBy = me !== account.get('id') && account.getIn(['relationship', 'followed_by']);
return (
<div className={classNames('profile-hover-card', { 'profile-hover-card--visible': visible })} ref={setPopperElement} style={styles.popper} {...attributes.popper} onMouseEnter={handleMouseEnter(dispatch)} onMouseLeave={handleMouseLeave(dispatch)}>

Wyświetl plik

@ -117,11 +117,11 @@ class StatusActionBar extends ImmutablePureComponent {
]
handleReplyClick = () => {
const { me } = this.props;
const { me, onReply, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.onReply(this.props.status, this.context.router.history);
onReply(status, this.context.router.history);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('REPLY');
}
}
@ -167,22 +167,22 @@ class StatusActionBar extends ImmutablePureComponent {
handleReactClick = emoji => {
return e => {
const { me, status } = this.props;
const { me, dispatch, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.dispatch(simpleEmojiReact(status, emoji));
dispatch(simpleEmojiReact(status, emoji));
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('FAVOURITE');
}
this.setState({ emojiSelectorVisible: false });
};
}
handleFavouriteClick = () => {
const { me } = this.props;
const { me, onFavourite, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.onFavourite(this.props.status);
onFavourite(status);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('FAVOURITE');
}
}
@ -191,11 +191,11 @@ class StatusActionBar extends ImmutablePureComponent {
}
handleReblogClick = e => {
const { me } = this.props;
const { me, onReblog, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.onReblog(this.props.status, e);
onReblog(status, e);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('REBLOG');
}
}
@ -599,10 +599,13 @@ const mapStateToProps = state => {
};
};
const mapDispatchToProps = (dispatch) => ({
const mapDispatchToProps = (dispatch, { status }) => ({
dispatch,
onOpenUnauthorizedModal() {
dispatch(openModal('UNAUTHORIZED'));
onOpenUnauthorizedModal(action) {
dispatch(openModal('UNAUTHORIZED', {
action,
ap_id: status.get('url'),
}));
},
});

Wyświetl plik

@ -242,7 +242,7 @@ class StatusContent extends React.PureComponent {
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
{!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} status={status.get('url')} />}
</div>
);
} else if (this.props.onClick) {
@ -265,7 +265,7 @@ class StatusContent extends React.PureComponent {
}
if (status.get('poll')) {
output.push(<PollContainer pollId={status.get('poll')} key='poll' />);
output.push(<PollContainer pollId={status.get('poll')} key='poll' status={status.get('url')} />);
}
return output;
@ -285,7 +285,7 @@ class StatusContent extends React.PureComponent {
];
if (status.get('poll')) {
output.push(<PollContainer pollId={status.get('poll')} key='poll' />);
output.push(<PollContainer pollId={status.get('poll')} key='poll' status={status.get('url')} />);
}
return output;

Wyświetl plik

@ -6,4 +6,5 @@ const mapStateToProps = (state, { pollId }) => ({
me: state.get('me'),
});
export default connect(mapStateToProps)(Poll);

Wyświetl plik

@ -44,10 +44,10 @@ const messages = defineMessages({
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete' },
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' },
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' },
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' },
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
});
@ -68,178 +68,180 @@ const makeMapStateToProps = () => {
return mapStateToProps;
};
const mapDispatchToProps = (dispatch, { intl }) => ({
onReply(status, router) {
dispatch((_, getState) => {
const state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, router)),
}));
} else {
dispatch(replyCompose(status, router));
}
});
},
onModalReblog(status) {
const mapDispatchToProps = (dispatch, { intl }) => {
function onModalReblog(status) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
dispatch(reblog(status));
}
},
}
onReblog(status, e) {
dispatch((_, getState) => {
const boostModal = getSettings(getState()).get('boostModal');
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
return {
onReply(status, router) {
dispatch((_, getState) => {
const state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
dispatch(openModal('CONFIRM', {
message: intl.formatMessage(messages.replyMessage),
confirm: intl.formatMessage(messages.replyConfirm),
onConfirm: () => dispatch(replyCompose(status, router)),
}));
} else {
dispatch(replyCompose(status, router));
}
});
},
onModalReblog,
onReblog(status, e) {
dispatch((_, getState) => {
const boostModal = getSettings(getState()).get('boostModal');
if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
} else {
dispatch(openModal('BOOST', { status, onReblog: onModalReblog }));
}
});
},
onFavourite(status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
} else {
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
dispatch(favourite(status));
}
});
},
},
onFavourite(status) {
if (status.get('favourited')) {
dispatch(unfavourite(status));
} else {
dispatch(favourite(status));
}
},
onBookmark(status) {
if (status.get('bookmarked')) {
dispatch(unbookmark(intl, status));
} else {
dispatch(bookmark(intl, status));
}
},
onPin(status) {
if (status.get('pinned')) {
dispatch(unpin(status));
} else {
dispatch(pin(status));
}
},
onEmbed(status) {
dispatch(openModal('EMBED', {
url: status.get('url'),
onError: error => dispatch(showAlertForError(error)),
}));
},
onDelete(status, history, withRedraft = false) {
dispatch((_, getState) => {
const deleteModal = getSettings(getState()).get('deleteModal');
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft));
onBookmark(status) {
if (status.get('bookmarked')) {
dispatch(unbookmark(intl, status));
} else {
dispatch(openModal('CONFIRM', {
icon: withRedraft ? require('@tabler/icons/icons/edit.svg') : require('@tabler/icons/icons/trash.svg'),
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
}));
dispatch(bookmark(intl, status));
}
});
},
},
onDirect(account, router) {
dispatch(directCompose(account, router));
},
onPin(status) {
if (status.get('pinned')) {
dispatch(unpin(status));
} else {
dispatch(pin(status));
}
},
onChat(account, router) {
dispatch(launchChat(account.get('id'), router));
},
onEmbed(status) {
dispatch(openModal('EMBED', {
url: status.get('url'),
onError: error => dispatch(showAlertForError(error)),
}));
},
onMention(account, router) {
dispatch(mentionCompose(account, router));
},
onDelete(status, history, withRedraft = false) {
dispatch((_, getState) => {
const deleteModal = getSettings(getState()).get('deleteModal');
if (!deleteModal) {
dispatch(deleteStatus(status.get('id'), history, withRedraft));
} else {
dispatch(openModal('CONFIRM', {
icon: withRedraft ? require('@tabler/icons/icons/edit.svg') : require('@tabler/icons/icons/trash.svg'),
heading: intl.formatMessage(withRedraft ? messages.redraftHeading : messages.deleteHeading),
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
}));
}
});
},
onOpenMedia(media, index) {
dispatch(openModal('MEDIA', { media, index }));
},
onDirect(account, router) {
dispatch(directCompose(account, router));
},
onOpenVideo(media, time) {
dispatch(openModal('VIDEO', { media, time }));
},
onChat(account, router) {
dispatch(launchChat(account.get('id'), router));
},
onOpenAudio(media, time) {
dispatch(openModal('AUDIO', { media, time }));
},
onMention(account, router) {
dispatch(mentionCompose(account, router));
},
onBlock(status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/icons/ban.svg'),
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.get('acct') }} />,
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
},
onOpenMedia(media, index) {
dispatch(openModal('MEDIA', { media, index }));
},
onReport(status) {
dispatch(initReport(status.get('account'), status));
},
onOpenVideo(media, time) {
dispatch(openModal('VIDEO', { media, time }));
},
onMute(account) {
dispatch(initMuteModal(account));
},
onOpenAudio(media, time) {
dispatch(openModal('AUDIO', { media, time }));
},
onMuteConversation(status) {
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')));
} else {
dispatch(muteStatus(status.get('id')));
}
},
onBlock(status) {
const account = status.get('account');
dispatch(openModal('CONFIRM', {
icon: require('@tabler/icons/icons/ban.svg'),
heading: <FormattedMessage id='confirmations.block.heading' defaultMessage='Block @{name}' values={{ name: account.get('acct') }} />,
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
confirm: intl.formatMessage(messages.blockConfirm),
onConfirm: () => dispatch(blockAccount(account.get('id'))),
secondary: intl.formatMessage(messages.blockAndReport),
onSecondary: () => {
dispatch(blockAccount(account.get('id')));
dispatch(initReport(account, status));
},
}));
},
onToggleHidden(status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
onReport(status) {
dispatch(initReport(status.get('account'), status));
},
onGroupRemoveAccount(groupId, accountId) {
dispatch(createRemovedAccount(groupId, accountId));
},
onMute(account) {
dispatch(initMuteModal(account));
},
onGroupRemoveStatus(groupId, statusId) {
dispatch(groupRemoveStatus(groupId, statusId));
},
onMuteConversation(status) {
if (status.get('muted')) {
dispatch(unmuteStatus(status.get('id')));
} else {
dispatch(muteStatus(status.get('id')));
}
},
onDeactivateUser(status) {
dispatch(deactivateUserModal(intl, status.getIn(['account', 'id'])));
},
onToggleHidden(status) {
if (status.get('hidden')) {
dispatch(revealStatus(status.get('id')));
} else {
dispatch(hideStatus(status.get('id')));
}
},
onDeleteUser(status) {
dispatch(deleteUserModal(intl, status.getIn(['account', 'id'])));
},
onGroupRemoveAccount(groupId, accountId) {
dispatch(createRemovedAccount(groupId, accountId));
},
onDeleteStatus(status) {
dispatch(deleteStatusModal(intl, status.get('id')));
},
onGroupRemoveStatus(groupId, statusId) {
dispatch(groupRemoveStatus(groupId, statusId));
},
onToggleStatusSensitivity(status) {
dispatch(toggleStatusSensitivityModal(intl, status.get('id'), status.get('sensitive')));
},
onDeactivateUser(status) {
dispatch(deactivateUserModal(intl, status.getIn(['account', 'id'])));
},
});
onDeleteUser(status) {
dispatch(deleteUserModal(intl, status.getIn(['account', 'id'])));
},
onDeleteStatus(status) {
dispatch(deleteStatusModal(intl, status.get('id')));
},
onToggleStatusSensitivity(status) {
dispatch(toggleStatusSensitivityModal(intl, status.get('id'), status.get('sensitive')));
},
};
};
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Status));

Wyświetl plik

@ -356,7 +356,7 @@ class Header extends ImmutablePureComponent {
});
}
if (account.get('id') !== me && isLocal(account)) {
if (account.get('id') !== me && isLocal(account) && isAdmin(meAccount)) {
if (isAdmin(account)) {
menu.push({
text: intl.formatMessage(messages.demoteToModerator, { name: account.get('username') }),
@ -407,7 +407,7 @@ class Header extends ImmutablePureComponent {
});
}
if (features.suggestionsV2) {
if (features.suggestionsV2 && isAdmin(meAccount)) {
if (account.getIn(['pleroma', 'is_suggested'])) {
menu.push({
text: intl.formatMessage(messages.unsuggestUser, { name: account.get('username') }),

Wyświetl plik

@ -8,7 +8,7 @@ import {
} from 'soapbox/actions/accounts';
import { expandAccountMediaTimeline } from '../../actions/timelines';
import LoadingIndicator from 'soapbox/components/loading_indicator';
import Column from '../ui/components/column';
import Column from 'soapbox/components/column';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { getAccountGallery, findAccountByUsername } from 'soapbox/selectors';
import MediaItem from './components/media_item';
@ -17,6 +17,7 @@ import MissingIndicator from 'soapbox/components/missing_indicator';
import { openModal } from 'soapbox/actions/modal';
import { NavLink } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import SubNavigation from 'soapbox/components/sub_navigation';
const mapStateToProps = (state, { params, withReplies = false }) => {
const username = params.username || '';
@ -186,6 +187,7 @@ class AccountGallery extends ImmutablePureComponent {
return (
<Column>
<SubNavigation message={`@${accountUsername}`} />
<div className='slist slist--flex' onScroll={this.handleScroll}>
<div className='account__section-headline'>
<div style={{ width: '100%', display: 'flex' }}>

Wyświetl plik

@ -12,6 +12,7 @@ import { SimpleForm, TextInput } from 'soapbox/features/forms';
import { Set as ImmutableSet, OrderedSet as ImmutableOrderedSet, is } from 'immutable';
const messages = defineMessages({
heading: { id: 'column.admin.users', defaultMessage: 'Users' },
empty: { id: 'admin.user_index.empty', defaultMessage: 'No users found.' },
searchPlaceholder: { id: 'admin.user_index.search_input_placeholder', defaultMessage: 'Who are you looking for?' },
});
@ -100,7 +101,7 @@ class UserIndex extends ImmutablePureComponent {
const showLoading = isLoading && accountIds.isEmpty();
return (
<Column>
<Column heading={intl.formatMessage(messages.heading)}>
<SimpleForm style={{ paddingBottom: 0 }}>
<TextInput
onChange={this.handleQueryChange}

Wyświetl plik

@ -2,6 +2,7 @@ import React from 'react';
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
import { connect } from 'react-redux';
import classNames from 'classnames';
import { makeGetAccount } from 'soapbox/selectors';
@ -18,6 +19,7 @@ const makeMapStateToProps = () => {
const getAccount = makeGetAccount();
const mapStateToProps = (state, { id }) => ({
me: state.get('me'),
account: getAccount(state, id),
autoPlayGif: getSettings(state).get('autoPlayGif'),
});
@ -30,15 +32,24 @@ export default @injectIntl
class AccountCard extends ImmutablePureComponent {
static propTypes = {
me: SoapboxPropTypes.me,
account: ImmutablePropTypes.map.isRequired,
autoPlayGif: PropTypes.bool,
};
render() {
const { account, autoPlayGif } = this.props;
const { account, autoPlayGif, me } = this.props;
const followedBy = me !== account.get('id') && account.getIn(['relationship', 'followed_by']);
return (
<div className='directory__card'>
{followedBy &&
<div className='directory__card__info'>
<span className='relationship-tag'>
<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />
</span>
</div>}
<div className='directory__card__action-button'>
<ActionButton account={account} small />
</div>

Wyświetl plik

@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { fetchFavouritedStatuses, expandFavouritedStatuses, fetchAccountFavouritedStatuses, expandAccountFavouritedStatuses } from '../../actions/favourites';
import Column from '../ui/components/column';
import StatusList from '../../components/status_list';
import { injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import MissingIndicator from 'soapbox/components/missing_indicator';
@ -13,6 +13,10 @@ import { fetchAccount, fetchAccountByUsername } from '../../actions/accounts';
import LoadingIndicator from '../../components/loading_indicator';
import { findAccountByUsername } from 'soapbox/selectors';
const messages = defineMessages({
heading: { id: 'column.favourited_statuses', defaultMessage: 'Liked posts' },
});
const mapStateToProps = (state, { params }) => {
const username = params.username || '';
const me = state.get('me');
@ -102,7 +106,7 @@ class Favourites extends ImmutablePureComponent {
}, 300, { leading: true })
render() {
const { statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props;
const { intl, statusIds, isLoading, hasMore, isMyAccount, isAccount, accountId, unavailable } = this.props;
if (!isMyAccount && !isAccount && accountId !== -1) {
return (
@ -135,7 +139,7 @@ class Favourites extends ImmutablePureComponent {
: <FormattedMessage id='empty_column.account_favourited_statuses' defaultMessage="This user doesn't have any liked posts yet." />;
return (
<Column>
<Column heading={intl.formatMessage(messages.heading)}>
<StatusList
statusIds={statusIds}
scrollKey='favourited_statuses'

Wyświetl plik

@ -215,54 +215,50 @@ class Filters extends ImmutablePureComponent {
<Button className='button button-primary setup' text={intl.formatMessage(messages.add_new)} onClick={this.handleAddNew} />
<ColumnSubheading text={intl.formatMessage(messages.subheading_filters)} />
<ScrollableList
scrollKey='filters'
emptyMessage={emptyMessage}
>
{filters.map((filter, i) => (
<div key={i} className='filter__container'>
<div className='filter__details'>
<div className='filter__phrase'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_phrase_label' defaultMessage='Keyword or phrase:' /></span>
<span className='filter__list-value'>{filter.get('phrase')}</span>
</div>
<div className='filter__contexts'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' /></span>
<span className='filter__list-value'>
{filter.get('context').map((context, i) => (
<span key={i} className='context'>{context}</span>
))}
</span>
</div>
<div className='filter__details'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_details_label' defaultMessage='Filter settings:' /></span>
<span className='filter__list-value'>
{filter.get('irreversible') ?
<span><FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /></span> :
<span><FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' /></span>
}
{filter.get('whole_word') &&
<span><FormattedMessage id='filters.filters_list_whole-word' defaultMessage='Whole word' /></span>
}
</span>
</div>
</div>
<div className='filter__delete' role='button' tabIndex='0' onClick={this.handleFilterDelete} data-value={filter.get('id')} aria-label={intl.formatMessage(messages.delete)}>
<Icon className='filter__delete-icon' id='times' size={40} />
<span className='filter__delete-label'><FormattedMessage id='filters.filters_list_delete' defaultMessage='Delete' /></span>
</div>
</div>
))}
</ScrollableList>
</div>
</SimpleForm>
<ColumnSubheading text={intl.formatMessage(messages.subheading_filters)} />
<ScrollableList
scrollKey='filters'
emptyMessage={emptyMessage}
>
{filters.map((filter, i) => (
<div key={i} className='filter__container'>
<div className='filter__details'>
<div className='filter__phrase'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_phrase_label' defaultMessage='Keyword or phrase:' /></span>
<span className='filter__list-value'>{filter.get('phrase')}</span>
</div>
<div className='filter__contexts'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_context_label' defaultMessage='Filter contexts:' /></span>
<span className='filter__list-value'>
{filter.get('context').map((context, i) => (
<span key={i} className='context'>{context}</span>
))}
</span>
</div>
<div className='filter__details'>
<span className='filter__list-label'><FormattedMessage id='filters.filters_list_details_label' defaultMessage='Filter settings:' /></span>
<span className='filter__list-value'>
{filter.get('irreversible') ?
<span><FormattedMessage id='filters.filters_list_drop' defaultMessage='Drop' /></span> :
<span><FormattedMessage id='filters.filters_list_hide' defaultMessage='Hide' /></span>
}
{filter.get('whole_word') &&
<span><FormattedMessage id='filters.filters_list_whole-word' defaultMessage='Whole word' /></span>
}
</span>
</div>
</div>
<div className='filter__delete' role='button' tabIndex='0' onClick={this.handleFilterDelete} data-value={filter.get('id')} aria-label={intl.formatMessage(messages.delete)}>
<Icon className='filter__delete-icon' id='times' size={40} />
<span className='filter__delete-label'><FormattedMessage id='filters.filters_list_delete' defaultMessage='Delete' /></span>
</div>
</div>
))}
</ScrollableList>
</Column>
);
}

Wyświetl plik

@ -11,7 +11,7 @@ import {
expandFollowers,
fetchAccountByUsername,
} from '../../actions/accounts';
import { FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list';
@ -19,6 +19,10 @@ import MissingIndicator from 'soapbox/components/missing_indicator';
import { getFollowDifference } from 'soapbox/utils/accounts';
import { findAccountByUsername } from 'soapbox/selectors';
const messages = defineMessages({
heading: { id: 'column.followers', defaultMessage: 'Followers' },
});
const mapStateToProps = (state, { params, withReplies = false }) => {
const username = params.username || '';
const me = state.get('me');
@ -47,9 +51,11 @@ const mapStateToProps = (state, { params, withReplies = false }) => {
};
export default @connect(mapStateToProps)
@injectIntl
class Followers extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.orderedSet,
@ -85,7 +91,7 @@ class Followers extends ImmutablePureComponent {
}, 300, { leading: true });
render() {
const { accountIds, hasMore, diffCount, isAccount, accountId, unavailable } = this.props;
const { intl, accountIds, hasMore, diffCount, isAccount, accountId, unavailable } = this.props;
if (!isAccount && accountId !== -1) {
return (
@ -114,7 +120,7 @@ class Followers extends ImmutablePureComponent {
}
return (
<Column>
<Column heading={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='followers'
hasMore={hasMore}

Wyświetl plik

@ -11,7 +11,7 @@ import {
expandFollowing,
fetchAccountByUsername,
} from '../../actions/accounts';
import { FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list';
@ -19,6 +19,10 @@ import MissingIndicator from 'soapbox/components/missing_indicator';
import { getFollowDifference } from 'soapbox/utils/accounts';
import { findAccountByUsername } from 'soapbox/selectors';
const messages = defineMessages({
heading: { id: 'column.following', defaultMessage: 'Following' },
});
const mapStateToProps = (state, { params, withReplies = false }) => {
const username = params.username || '';
const me = state.get('me');
@ -47,9 +51,11 @@ const mapStateToProps = (state, { params, withReplies = false }) => {
};
export default @connect(mapStateToProps)
@injectIntl
class Following extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.orderedSet,
@ -85,7 +91,7 @@ class Following extends ImmutablePureComponent {
}, 300, { leading: true });
render() {
const { accountIds, hasMore, isAccount, diffCount, accountId, unavailable } = this.props;
const { intl, accountIds, hasMore, isAccount, diffCount, accountId, unavailable } = this.props;
if (!isAccount && accountId !== -1) {
return (
@ -114,7 +120,7 @@ class Following extends ImmutablePureComponent {
}
return (
<Column>
<Column heading={intl.formatMessage(messages.heading)}>
<ScrollableList
scrollKey='following'
hasMore={hasMore}

Wyświetl plik

@ -69,7 +69,7 @@ class ListAdder extends ImmutablePureComponent {
const { accountId, listIds, intl } = this.props;
return (
<div className='modal-root__modal compose-modal'>
<div className='modal-root__modal compose-modal list-editor__content'>
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>
<FormattedMessage id='list_adder.header_title' defaultMessage='Add or Remove from Lists' />

Wyświetl plik

@ -70,7 +70,7 @@ class ListEditor extends ImmutablePureComponent {
</h3>
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} src={require('@tabler/icons/icons/x.svg')} onClick={this.onClickClose} />
</div>
<div className='compose-modal__content'>
<div className='compose-modal__content list-editor__content'>
<div className='list-editor'>
<ColumnSubheading text={intl.formatMessage(messages.changeTitle)} />
<EditListForm />

Wyświetl plik

@ -5,10 +5,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
import Column from '../ui/components/column';
import StatusList from '../../components/status_list';
import { injectIntl, FormattedMessage } from 'react-intl';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import MissingIndicator from 'soapbox/components/missing_indicator';
const messages = defineMessages({
heading: { id: 'column.pins', defaultMessage: 'Pinned posts' },
});
const mapStateToProps = (state, { params }) => {
const username = params.username || '';
const me = state.get('me');
@ -37,7 +41,7 @@ class PinnedStatuses extends ImmutablePureComponent {
}
render() {
const { statusIds, hasMore, isMyAccount } = this.props;
const { intl, statusIds, hasMore, isMyAccount } = this.props;
if (!isMyAccount) {
return (
@ -48,7 +52,7 @@ class PinnedStatuses extends ImmutablePureComponent {
}
return (
<Column>
<Column heading={intl.formatMessage(messages.heading)}>
<StatusList
statusIds={statusIds}
scrollKey='pinned_statuses'

Wyświetl plik

@ -4,16 +4,20 @@ import { OrderedSet as ImmutableOrderedSet } from 'immutable';
import ImmutablePureComponent from 'react-immutable-pure-component';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import LoadingIndicator from '../../components/loading_indicator';
import MissingIndicator from '../../components/missing_indicator';
import { fetchFavourites, fetchReactions } from '../../actions/interactions';
import { fetchStatus } from '../../actions/statuses';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container';
import Column from '../ui/components/column';
import ScrollableList from '../../components/scrollable_list';
import { makeGetStatus } from '../../selectors';
const messages = defineMessages({
heading: { id: 'column.reactions', defaultMessage: 'Reactions' },
});
const mapStateToProps = (state, props) => {
const getStatus = makeGetStatus();
const status = getStatus(state, {
@ -35,6 +39,7 @@ const mapStateToProps = (state, props) => {
};
export default @connect(mapStateToProps)
@injectIntl
class Reactions extends ImmutablePureComponent {
static contextTypes = {
@ -78,14 +83,11 @@ class Reactions extends ImmutablePureComponent {
};
render() {
const { params, reactions, accounts, status } = this.props;
const { username, statusId } = params;
const back = `/@${username}/posts/${statusId}`;
const { intl, params, reactions, accounts, status } = this.props;
if (!accounts) {
return (
<Column back={back}>
<Column>
<LoadingIndicator />
</Column>
);
@ -93,7 +95,7 @@ class Reactions extends ImmutablePureComponent {
if (!status) {
return (
<Column back={back}>
<Column>
<MissingIndicator />
</Column>
);
@ -102,7 +104,7 @@ class Reactions extends ImmutablePureComponent {
const emptyMessage = <FormattedMessage id='status.reactions.empty' defaultMessage='No one has reacted to this post yet. When someone does, they will show up here.' />;
return (
<Column back={back}>
<Column heading={intl.formatMessage(messages.heading)}>
{
reactions.size > 0 && (
<div className='reaction__filter-bar'>

Wyświetl plik

@ -66,9 +66,12 @@ const mapStateToProps = state => {
};
};
const mapDispatchToProps = (dispatch) => ({
onOpenUnauthorizedModal() {
dispatch(openModal('UNAUTHORIZED'));
const mapDispatchToProps = (dispatch, { status }) => ({
onOpenUnauthorizedModal(action) {
dispatch(openModal('UNAUTHORIZED', {
action,
ap_id: status.get('url'),
}));
},
});
@ -121,20 +124,20 @@ class ActionBar extends React.PureComponent {
}
handleReplyClick = () => {
const { me } = this.props;
const { me, onReply, onOpenUnauthorizedModal } = this.props;
if (me) {
this.props.onReply(this.props.status);
onReply(this.props.status);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('REPLY');
}
}
handleReblogClick = (e) => {
const { me } = this.props;
const { me, onReblog, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.onReblog(this.props.status, e);
onReblog(status, e);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('REBLOG');
}
}
@ -143,11 +146,11 @@ class ActionBar extends React.PureComponent {
}
handleFavouriteClick = () => {
const { me } = this.props;
const { me, onFavourite, onOpenUnauthorizedModal } = this.props;
if (me) {
this.props.onFavourite(this.props.status);
onFavourite(status);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('FAVOURITE');
}
}
@ -184,11 +187,11 @@ class ActionBar extends React.PureComponent {
handleReactClick = emoji => {
return e => {
const { me } = this.props;
const { me, onEmojiReact, onOpenUnauthorizedModal, status } = this.props;
if (me) {
this.props.onEmojiReact(this.props.status, emoji);
onEmojiReact(status, emoji);
} else {
this.props.onOpenUnauthorizedModal();
onOpenUnauthorizedModal('FAVOURITE');
}
this.setState({ emojiSelectorVisible: false, emojiSelectorFocused: false });
};

Wyświetl plik

@ -13,6 +13,8 @@ import {
blockAccount,
unblockAccount,
} from 'soapbox/actions/accounts';
import { openModal } from 'soapbox/actions/modal';
import { getFeatures } from 'soapbox/utils/features';
const messages = defineMessages({
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
@ -26,8 +28,11 @@ const messages = defineMessages({
const mapStateToProps = state => {
const me = state.get('me');
const instance = state.get('instance');
return {
me,
features: getFeatures(instance),
};
};
@ -47,6 +52,14 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(blockAccount(account.get('id')));
}
},
onOpenUnauthorizedModal(account) {
dispatch(openModal('UNAUTHORIZED', {
action: 'FOLLOW',
account: account.get('id'),
ap_id: account.get('url'),
}));
},
});
export default @connect(mapStateToProps, mapDispatchToProps)
@ -57,8 +70,10 @@ class ActionButton extends ImmutablePureComponent {
account: ImmutablePropTypes.map.isRequired,
onFollow: PropTypes.func.isRequired,
onBlock: PropTypes.func.isRequired,
onOpenUnauthorizedModal: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
small: PropTypes.bool,
features: PropTypes.object.isRequired,
};
static defaultProps = {
@ -81,12 +96,26 @@ class ActionButton extends ImmutablePureComponent {
this.props.onBlock(this.props.account);
}
handleRemoteFollow = () => {
this.props.onOpenUnauthorizedModal(this.props.account);
}
render() {
const { account, intl, me, small } = this.props;
const { account, intl, me, small, features } = this.props;
const empty = <></>;
if (!me) {
// Remote follow
if (features.remoteInteractionsAPI) {
return (<Button
className='button--follow'
onClick={this.handleRemoteFollow}
>
{intl.formatMessage(messages.follow)}
<Icon src={require('@tabler/icons/icons/plus.svg')} />
</Button>);
}
return (<form method='POST' action='/main/ostatus'>
<input type='hidden' name='nickname' value={account.get('acct')} />
<input type='hidden' name='profile' value='' />

Wyświetl plik

@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { defineMessages, injectIntl } from 'react-intl';
import Column from './column';
import ColumnHeader from './column_header';
const messages = defineMessages({
title: { id: 'column_forbidden.title', defaultMessage: 'Forbidden' },
body: { id: 'column_forbidden.body', defaultMessage: 'You do not have permission to access this page.' },
});
class ColumnForbidden extends React.PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl: { formatMessage } } = this.props;
return (
<Column>
<ColumnHeader icon='exclamation-circle' type={formatMessage(messages.title)} />
<div className='error-column'>
{formatMessage(messages.body)}
</div>
</Column>
);
}
}
export default injectIntl(ColumnForbidden);

Wyświetl plik

@ -5,32 +5,150 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import ImmutablePureComponent from 'react-immutable-pure-component';
import IconButton from 'soapbox/components/icon_button';
import snackbar from 'soapbox/actions/snackbar';
import { remoteInteraction } from 'soapbox/actions/interactions';
import { getFeatures } from 'soapbox/utils/features';
const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' },
accountPlaceholder: { id: 'remote_interaction.account_placeholder', defaultMessage: 'Enter your username@domain you want to act from' },
userNotFoundError: { id: 'remote_interaction.user_not_found_error', defaultMessage: 'Couldn\'t find given user' },
});
const mapStateToProps = state => {
const me = state.get('me');
const mapStateToProps = (state, props) => {
const instance = state.get('instance');
const features = getFeatures(instance);
if (props.action !== 'FOLLOW') {
return {
features,
siteTitle: state.getIn(['instance', 'title']),
remoteInteractionsAPI: features.remoteInteractionsAPI,
};
}
const userName = state.getIn(['accounts', props.account, 'display_name']);
return {
account: state.getIn(['accounts', me]),
features,
siteTitle: state.getIn(['instance', 'title']),
userName,
remoteInteractionsAPI: features.remoteInteractionsAPI,
};
};
const mapDispatchToProps = dispatch => ({
dispatch,
onRemoteInteraction(ap_id, account) {
return dispatch(remoteInteraction(ap_id, account));
},
});
class UnauthorizedModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
features: PropTypes.object.isRequired,
onClose: PropTypes.func.isRequired,
onRemoteInteraction: PropTypes.func.isRequired,
userName: PropTypes.string,
};
state = {
account: '',
};
onAccountChange = e => {
this.setState({ account: e.target.value });
}
onClickClose = () => {
this.props.onClose('UNAUTHORIZED');
};
onClickProceed = e => {
e.preventDefault();
const { intl, ap_id, dispatch, onClose, onRemoteInteraction } = this.props;
const { account } = this.state;
onRemoteInteraction(ap_id, account)
.then(url => {
window.open(url, '_new', 'noopener,noreferrer');
onClose('UNAUTHORIZED');
})
.catch(error => {
if (error.message === 'Couldn\'t find user') {
dispatch(snackbar.error(intl.formatMessage(messages.userNotFoundError)));
}
});
}
renderRemoteInteractions() {
const { intl, siteTitle, userName, action } = this.props;
const { account } = this.state;
let header;
let button;
if (action === 'FOLLOW') {
header = <FormattedMessage id='remote_interaction.follow_title' defaultMessage='Follow {user} remotely' values={{ user: userName }} />;
button = <FormattedMessage id='remote_interaction.follow' defaultMessage='Proceed to follow' />;
} else if (action === 'REPLY') {
header = <FormattedMessage id='remote_interaction.reply_title' defaultMessage='Reply to a post remotely' />;
button = <FormattedMessage id='remote_interaction.reply' defaultMessage='Proceed to reply' />;
} else if (action === 'REBLOG') {
header = <FormattedMessage id='remote_interaction.reblog_title' defaultMessage='Reblog a post remotely' />;
button = <FormattedMessage id='remote_interaction.reblog' defaultMessage='Proceed to repost' />;
} else if (action === 'FAVOURITE') {
header = <FormattedMessage id='remote_interaction.favourite_title' defaultMessage='Like a post remotely' />;
button = <FormattedMessage id='remote_interaction.favourite' defaultMessage='Proceed to like' />;
} else if (action === 'POLL_VOTE') {
header = <FormattedMessage id='remote_interaction.poll_vote_title' defaultMessage='Vote in a poll remotely' />;
button = <FormattedMessage id='remote_interaction.poll_vote' defaultMessage='Proceed to vote' />;
}
return (
<div className='modal-root__modal compose-modal unauthorized-modal remote-interaction-modal'>
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>{header}</h3>
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} src={require('@tabler/icons/icons/x.svg')} onClick={this.onClickClose} />
</div>
<div className='remote-interaction-modal__content'>
<form className='simple_form remote-interaction-modal__fields'>
<input
type='text'
placeholder={intl.formatMessage(messages.accountPlaceholder)}
name='remote_follow[acct]'
value={account}
autoCorrect='off'
autoCapitalize='off'
onChange={this.onAccountChange}
required
/>
<button className='button' onClick={this.onClickProceed}>{button}</button>
</form>
<div className='remote-interaction-modal__divider'>
<span>
<FormattedMessage id='remote_interaction.divider' defaultMessage='or' />
</span>
</div>
<h3 className='compose-modal__header__title'><FormattedMessage id='unauthorized_modal.title' defaultMessage='Sign up for {site_title}' values={{ site_title: siteTitle }} /></h3>
<Link to='/' className='unauthorized-modal-content__button button' onClick={this.onClickClose}>
<FormattedMessage id='account.register' defaultMessage='Sign up' />
</Link>
<Link to='/auth/sign_in' className='unauthorized-modal-content__button button button-secondary' onClick={this.onClickClose}>
<FormattedMessage id='account.login' defaultMessage='Log in' />
</Link>
</div>
</div>
);
}
render() {
const { intl, siteTitle } = this.props;
const { intl, features, siteTitle } = this.props;
if (features.remoteInteractionsAPI && features.federating) return this.renderRemoteInteractions();
return (
<div className='modal-root__modal compose-modal unauthorized-modal'>
@ -61,4 +179,4 @@ class UnauthorizedModal extends ImmutablePureComponent {
}
export default injectIntl(connect(mapStateToProps)(UnauthorizedModal));
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(UnauthorizedModal));

Wyświetl plik

@ -317,14 +317,14 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/settings/import' page={DefaultPage} component={ImportData} content={children} />
<WrappedRoute path='/settings/aliases' page={DefaultPage} component={Aliases} content={children} />
<WrappedRoute path='/backups' page={DefaultPage} component={Backups} content={children} />
<WrappedRoute path='/soapbox/config' page={DefaultPage} component={SoapboxConfig} content={children} />
<WrappedRoute path='/soapbox/config' adminOnly page={DefaultPage} component={SoapboxConfig} content={children} />
<Redirect from='/admin/dashboard' to='/admin' exact />
<WrappedRoute path='/admin' page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/admin/approval' page={AdminPage} component={AwaitingApproval} content={children} exact />
<WrappedRoute path='/admin/reports' page={AdminPage} component={Reports} content={children} exact />
<WrappedRoute path='/admin/log' page={AdminPage} component={ModerationLog} content={children} exact />
<WrappedRoute path='/admin/users' page={AdminPage} component={UserIndex} content={children} exact />
<WrappedRoute path='/admin' staffOnly page={AdminPage} component={Dashboard} content={children} exact />
<WrappedRoute path='/admin/approval' staffOnly page={AdminPage} component={AwaitingApproval} content={children} exact />
<WrappedRoute path='/admin/reports' staffOnly page={AdminPage} component={Reports} content={children} exact />
<WrappedRoute path='/admin/log' staffOnly page={AdminPage} component={ModerationLog} content={children} exact />
<WrappedRoute path='/admin/users' staffOnly page={AdminPage} component={UserIndex} content={children} exact />
<WrappedRoute path='/info' page={EmptyPage} component={ServerInfo} content={children} />
<WrappedRoute path='/developers/apps/create' page={DefaultPage} component={CreateApp} content={children} />

Wyświetl plik

@ -1,16 +1,20 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { Redirect, Route } from 'react-router-dom';
import ColumnsAreaContainer from '../containers/columns_area_container';
import ColumnLoading from '../components/column_loading';
import ColumnForbidden from '../components/column_forbidden';
import BundleColumnError from '../components/bundle_column_error';
import BundleContainer from '../containers/bundle_container';
import { isStaff, isAdmin } from 'soapbox/utils/accounts';
const mapStateToProps = state => {
const me = state.get('me');
return {
me: state.get('me'),
account: state.getIn(['accounts', me]),
};
};
@ -22,8 +26,10 @@ class WrappedRoute extends React.Component {
content: PropTypes.node,
componentParams: PropTypes.object,
layout: PropTypes.object,
account: ImmutablePropTypes.map,
publicRoute: PropTypes.bool,
me: SoapboxPropTypes.me,
staffOnly: PropTypes.bool,
adminOnly: PropTypes.bool,
};
static defaultProps = {
@ -72,6 +78,14 @@ class WrappedRoute extends React.Component {
);
}
renderForbidden = () => {
return (
<ColumnsAreaContainer layout={this.props.layout}>
<ColumnForbidden />
</ColumnsAreaContainer>
);
}
renderError = (props) => {
return (
<ColumnsAreaContainer layout={this.props.layout}>
@ -80,16 +94,26 @@ class WrappedRoute extends React.Component {
);
}
render() {
const { component: Component, content, publicRoute, me, ...rest } = this.props;
loginRedirect = () => {
const actualUrl = encodeURIComponent(`${this.props.computedMatch.url}${this.props.location.search}`); // eslint-disable-line react/prop-types
return <Redirect to={`/auth/sign_in?redirect_uri=${actualUrl}`} />;
}
if (!publicRoute && me === false) {
const actualUrl = encodeURIComponent(`${this.props.computedMatch.url}${this.props.location.search}`); // eslint-disable-line react/prop-types
return <Redirect to={`/auth/sign_in?redirect_uri=${actualUrl}`} />;
// return <Route path={this.props.path} component={() => {
// window.location.href = `/auth/sign_in?redirect_uri=${actualUrl}`;
// return null;
// }}/>
render() {
const { component: Component, content, account, publicRoute, staffOnly, adminOnly, ...rest } = this.props;
const authorized = [
account || publicRoute,
staffOnly ? account && isStaff(account) : true,
adminOnly ? account && isAdmin(account) : true,
].every(c => c);
if (!authorized) {
if (!account) {
return this.loginRedirect();
} else {
return this.renderForbidden();
}
}
return <Route {...rest} render={this.renderComponent} />;

Wyświetl plik

@ -740,6 +740,18 @@
"remote_instance.federation_panel.some_restrictions_message": "{siteTitle} nakłada pewne ograniczenia na {host}.",
"remote_instance.pin_host": "Przypnij {instance}",
"remote_instance.unpin_host": "Odepnij {instance}",
"remote_interaction.account_placeholder": "Wprowadź nazwę@domenę użytkownika, z którego chcesz wykonać działanie",
"remote_interaction.favourite": "Przejdź do polubienia",
"remote_interaction.favourite_title": "Polub wpis zdalnie",
"remote_interaction.follow": "Przejdź do obserwacji",
"remote_interaction.follow_title": "Obserwuj {user} zdalnie",
"remote_interaction.poll_vote": "Przejdź do ankiety",
"remote_interaction.poll_vote_title": "Zagłosuj w ankiecie zdalnie",
"remote_interaction.reblog": "Przejdź do wpisu",
"remote_interaction.reblog_title": "Udostępnij wpis zdalnie",
"remote_interaction.reply": "Przejdź do odpowiedzi",
"remote_interaction.reply_title": "Odpowiedz na wpis zdalnie",
"remote_interaction.user_not_found_error": "Nie można odnaleźć podanego użytkownika",
"remote_timeline.filter_message": "Przeglądasz oś czasu {instance}",
"reply_indicator.cancel": "Anuluj",
"report.block": "Zablokuj {target}",

Wyświetl plik

@ -74,6 +74,7 @@ export const getFeatures = createSelector([
v.software === MASTODON && gte(v.compatVersion, '3.4.0'),
v.software === PLEROMA && gte(v.version, '2.4.50'),
]),
remoteInteractionsAPI: v.software === PLEROMA && gte(v.version, '2.4.50'),
};
});

Wyświetl plik

@ -956,7 +956,7 @@
// ScrollableList
.slist .item-list > article:first-child,
// ScrollableList placeholders
.slist .item-list .slist__placeholder > .material-status:first-child,
.slist .item-list .slist__placeholder:first-child > .material-status:first-child,
.slist.slist--flex .item-list > .material-status:first-child,
// Thread
.material-status:not(.material-status + .material-status) {

Wyświetl plik

@ -185,17 +185,29 @@
}
}
&__descendants .thread__status:first-child {
&__descendants &__status:first-child {
margin-top: 10px;
.status__wrapper--filtered {
margin-top: -10px;
}
}
&__status--focused:first-child,
&__ancestors &__status:first-child {
margin-top: 10px;
.status__wrapper--filtered {
margin-top: -10px;
}
}
&__descendants &__status:last-child {
margin-bottom: 10px;
.status__wrapper--filtered {
margin-bottom: -10px;
}
}
&__connector {

Wyświetl plik

@ -39,6 +39,13 @@
overflow: hidden;
position: relative;
&__info {
z-index: 1;
position: absolute;
top: 10px;
left: 10px;
}
&__action-button {
z-index: 1;
position: absolute;

Wyświetl plik

@ -14,6 +14,10 @@
border-radius: 8px 8px 0 0;
}
&__content {
padding: 0;
}
&__accounts {
background: var(--background-color);
overflow-y: auto;
@ -33,7 +37,7 @@
.search {
display: flex;
flex-direction: row;
margin: 10px 0;
margin: 10px;
> label {
flex: 1 1;

Wyświetl plik

@ -702,6 +702,12 @@
right: 15px;
height: 20px;
width: 20px;
.svg-icon {
color: var(--primary-text-color);
height: 20px;
width: 20px;
}
}
}
@ -854,6 +860,59 @@
}
}
.remote-interaction-modal {
&__content {
display: flex;
flex-direction: column;
// align-items: center;
row-gap: 10px;
padding: 10px;
.unauthorized-modal-content__button {
margin: 0 auto;
}
}
&__fields {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
.button {
width: auto;
margin: 0;
text-transform: none;
overflow: unset;
}
}
&__divider {
display: flex;
align-items: center;
gap: 10px;
margin: 0 -10px;
&::before,
&::after {
content: "";
flex: 1;
border-bottom: 1px solid hsla(var(--primary-text-color_hsl), 0.2);
}
}
@media screen and (max-width: 895px) {
margin: 0;
border-radius: 6px;
height: unset !important;
width: 440px !important;
}
@media screen and (max-width: 480px) {
width: 330px !important;
}
}
.focal-point-modal {
max-width: 80vw;
max-height: 80vh;

Wyświetl plik

@ -127,7 +127,7 @@
}
.status__wrapper--filtered {
color: var(--primary-text-color);
color: var(--primary-text-color--faint);
border: 0;
font-size: inherit;
text-align: center;
@ -137,7 +137,6 @@
box-sizing: border-box;
width: 100%;
clear: both;
border-bottom: 1px solid var(--brand-color--med);
}
.status__prepend-icon-wrapper {

Wyświetl plik

@ -169,7 +169,7 @@
}
&:hover::before {
z-index: 0;
z-index: -1;
content: "";
position: absolute;
top: 0;

Wyświetl plik

@ -496,7 +496,7 @@ code {
&::after {
display: flex;
align-items: center;
font-family: "Font Awesome 5 Free";
font-family: 'Font Awesome 5 Free';
content: "";
position: absolute;
right: 12px;
@ -504,6 +504,7 @@ code {
padding-left: 12px;
pointer-events: none;
margin-top: 8px;
font-weight: 900;
}
}

Wyświetl plik

@ -246,6 +246,10 @@
}
}
article:last-child > .domain {
border-bottom: none;
}
.domain__wrapper {
display: flex;
}

Wyświetl plik

@ -79,7 +79,7 @@
"detect-passive-events": "^2.0.0",
"dotenv": "^8.0.0",
"emoji-datasource": "5.0.0",
"emoji-mart": "^3.0.1",
"emoji-mart": "npm:emoji-mart-lazyload",
"entities": "^3.0.1",
"es6-symbol": "^3.1.1",
"escape-html": "^1.0.3",
@ -90,7 +90,7 @@
"http-link-header": "^1.0.2",
"immutable": "^4.0.0-rc.14",
"imports-loader": "^1.0.0",
"intersection-observer": "^0.11.0",
"intersection-observer": "^0.12.0",
"intl": "^1.2.5",
"intl-messageformat": "^9.0.0",
"intl-messageformat-parser": "^6.0.0",

Wyświetl plik

@ -3598,12 +3598,13 @@ emoji-datasource@5.0.0:
resolved "https://registry.yarnpkg.com/emoji-datasource/-/emoji-datasource-5.0.0.tgz#1522fdba3c52223a1cf5a1c1fc282935400eaa06"
integrity sha512-LuvLWFnxznTH++GytEzpzOPUo1SB+6CUFqIlVETJJ3x9fpyMCKFfyqberbhMLOpT1qcNe+km+zoyBeUSC3u5Rw==
emoji-mart@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/emoji-mart/-/emoji-mart-3.0.1.tgz#9ce86706e02aea0506345f98464814a662ca54c6"
integrity sha512-sxpmMKxqLvcscu6mFn9ITHeZNkGzIvD0BSNFE/LJESPbCA8s1jM6bCDPjWbV31xHq7JXaxgpHxLB54RCbBZSlg==
"emoji-mart@npm:emoji-mart-lazyload":
version "3.0.1-j"
resolved "https://registry.yarnpkg.com/emoji-mart-lazyload/-/emoji-mart-lazyload-3.0.1-j.tgz#87a90d30b79d9145ece078d53e3e683c1a10ce9c"
integrity sha512-0wKF7MR0/iAeCIoiBLY+JjXCugycTgYRC2SL0y9/bjNSQlbeMdzILmPQJAufU/mgLFDUitOvjxLDhOZ9yxZ48g==
dependencies:
"@babel/runtime" "^7.0.0"
intersection-observer "^0.12.0"
prop-types "^15.6.0"
emoji-regex@^8.0.0:
@ -4951,10 +4952,10 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
intersection-observer@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.11.0.tgz#f4ea067070326f68393ee161cc0a2ca4c0040c6f"
integrity sha512-KZArj2QVnmdud9zTpKf279m2bbGfG+4/kn16UU0NL3pTVl52ZHiJ9IRNSsnn6jaHrL9EGLFM5eWjTx2fz/+zoQ==
intersection-observer@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.0.tgz#6c84628f67ce8698e5f9ccf857d97718745837aa"
integrity sha512-2Vkz8z46Dv401zTWudDGwO7KiGHNDkMv417T5ItcNYfmvHR/1qCTVBO9vwH8zZmQ0WkA/1ARwpysR9bsnop4NQ==
intl-messageformat-parser@6.1.2:
version "6.1.2"