Skip to content

Commit

Permalink
Merge pull request #734 from kmycode/upstream-20240426
Browse files Browse the repository at this point in the history
Upstream 20240426
  • Loading branch information
kmycode authored Apr 26, 2024
2 parents d1d68e8 + c4017eb commit 081a9f5
Show file tree
Hide file tree
Showing 22 changed files with 390 additions and 147 deletions.
9 changes: 1 addition & 8 deletions app/javascript/mastodon/actions/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,6 @@ export function updateNotifications(notification, intlMessages, intlLocale) {
if (notification.status) {
dispatch(importFetchedStatus(notification.status));
}
if (notification.statuses) {
dispatch(importFetchedStatuses(notification.statuses));
}

if (notification.report) {
dispatch(importFetchedAccount(notification.report.target_account));
Expand Down Expand Up @@ -182,7 +179,6 @@ const excludeTypesFromFilter = filter => {
'status',
'list_status',
'update',
'account_warning',
'admin.sign_up',
'admin.report',
]);
Expand Down Expand Up @@ -241,10 +237,7 @@ export function expandNotifications({ maxId, forceLoad } = {}, done = noOp) {
const next = getLinks(response).refs.find(link => link.rel === 'next');

dispatch(importFetchedAccounts(response.data.map(item => item.account)));
dispatch(importFetchedStatuses(
response.data.map(item => item.status).filter(status => !!status)
.concat(response.data.flatMap(item => item.statuses || []))
));
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
dispatch(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account)));

dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
Expand Down
88 changes: 88 additions & 0 deletions app/javascript/mastodon/features/explore/components/card.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import PropTypes from 'prop-types';
import { useCallback } from 'react';

import { FormattedMessage, useIntl, defineMessages } from 'react-intl';

import { Link } from 'react-router-dom';

import { useDispatch, useSelector } from 'react-redux';

import CloseIcon from '@/material-icons/400-24px/close.svg?react';
import { followAccount, unfollowAccount } from 'mastodon/actions/accounts';
import { dismissSuggestion } from 'mastodon/actions/suggestions';
import { Avatar } from 'mastodon/components/avatar';
import { Button } from 'mastodon/components/button';
import { DisplayName } from 'mastodon/components/display_name';
import { IconButton } from 'mastodon/components/icon_button';
import { domain } from 'mastodon/initial_state';

const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
dismiss: { id: 'follow_suggestions.dismiss', defaultMessage: "Don't show again" },
});

export const Card = ({ id, source }) => {
const intl = useIntl();
const account = useSelector(state => state.getIn(['accounts', id]));
const relationship = useSelector(state => state.getIn(['relationships', id]));
const dispatch = useDispatch();
const following = relationship?.get('following') ?? relationship?.get('requested');

const handleFollow = useCallback(() => {
if (following) {
dispatch(unfollowAccount(id));
} else {
dispatch(followAccount(id));
}
}, [id, following, dispatch]);

const handleDismiss = useCallback(() => {
dispatch(dismissSuggestion(id));
}, [id, dispatch]);

let label;

switch (source) {
case 'friends_of_friends':
label = <FormattedMessage id='follow_suggestions.friends_of_friends_longer' defaultMessage='Popular among people you follow' />;
break;
case 'similar_to_recently_followed':
label = <FormattedMessage id='follow_suggestions.similar_to_recently_followed_longer' defaultMessage='Similar to profiles you recently followed' />;
break;
case 'featured':
label = <FormattedMessage id='follow_suggestions.featured_longer' defaultMessage='Hand-picked by the {domain} team' values={{ domain }} />;
break;
case 'most_followed':
label = <FormattedMessage id='follow_suggestions.popular_suggestion_longer' defaultMessage='Popular on {domain}' values={{ domain }} />;
break;
case 'most_interactions':
label = <FormattedMessage id='follow_suggestions.popular_suggestion_longer' defaultMessage='Popular on {domain}' values={{ domain }} />;
break;
}

return (
<div className='explore__suggestions__card'>
<div className='explore__suggestions__card__source'>
{label}
</div>

<div className='explore__suggestions__card__body'>
<Link to={`/@${account.get('acct')}`}><Avatar account={account} size={48} /></Link>

<div className='explore__suggestions__card__body__main'>
<div className='explore__suggestions__card__body__main__name-button'>
<Link className='explore__suggestions__card__body__main__name-button__name' to={`/@${account.get('acct')}`}><DisplayName account={account} /></Link>
<IconButton iconComponent={CloseIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
<Button text={intl.formatMessage(following ? messages.unfollow : messages.follow)} secondary={following} onClick={handleFollow} />
</div>
</div>
</div>
</div>
);
};

Card.propTypes = {
id: PropTypes.string.isRequired,
source: PropTypes.oneOf(['friends_of_friends', 'similar_to_recently_followed', 'featured', 'most_followed', 'most_interactions']),
};
9 changes: 7 additions & 2 deletions app/javascript/mastodon/features/explore/suggestions.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { connect } from 'react-redux';

import { fetchSuggestions } from 'mastodon/actions/suggestions';
import { LoadingIndicator } from 'mastodon/components/loading_indicator';
import AccountCard from 'mastodon/features/directory/components/account_card';
import { WithRouterPropTypes } from 'mastodon/utils/react_router';

import { Card } from './components/card';

const mapStateToProps = state => ({
suggestions: state.getIn(['suggestions', 'items']),
isLoading: state.getIn(['suggestions', 'isLoading']),
Expand Down Expand Up @@ -54,7 +55,11 @@ class Suggestions extends PureComponent {
return (
<div className='explore__suggestions scrollable' data-nosnippet>
{isLoading ? <LoadingIndicator /> : suggestions.map(suggestion => (
<AccountCard key={suggestion.get('account')} id={suggestion.get('account')} />
<Card
key={suggestion.get('account')}
id={suggestion.get('account')}
source={suggestion.getIn(['sources', 0])}
/>
))}
</div>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';

import WarningIcon from '@/material-icons/400-24px/warning-fill.svg?react';
import { Icon } from 'mastodon/components/icon';

// This needs to be kept in sync with app/models/account_warning.rb
const messages = defineMessages({
none: {
id: 'notification.moderation_warning.action_none',
defaultMessage: 'Your account has received a moderation warning.',
},
disable: {
id: 'notification.moderation_warning.action_disable',
defaultMessage: 'Your account has been disabled.',
},
force_cw: {
id: 'notification.moderation_warning.action_force_cw',
defaultMessage: 'Some of your posts have been added content-warning text.',
},
mark_statuses_as_sensitive: {
id: 'notification.moderation_warning.action_mark_statuses_as_sensitive',
defaultMessage: 'Some of your posts have been marked as sensitive.',
},
delete_statuses: {
id: 'notification.moderation_warning.action_delete_statuses',
defaultMessage: 'Some of your posts have been removed.',
},
sensitive: {
id: 'notification.moderation_warning.action_sensitive',
defaultMessage: 'Your posts will be marked as sensitive from now on.',
},
silence: {
id: 'notification.moderation_warning.action_silence',
defaultMessage: 'Your account has been limited.',
},
suspend: {
id: 'notification.moderation_warning.action_suspend',
defaultMessage: 'Your account has been suspended.',
},
});

interface Props {
action:
| 'none'
| 'disable'
| 'force_cw'
| 'mark_statuses_as_sensitive'
| 'delete_statuses'
| 'sensitive'
| 'silence'
| 'suspend';
id: string;
hidden: boolean;
}

export const ModerationWarning: React.FC<Props> = ({ action, id, hidden }) => {
const intl = useIntl();

if (hidden) {
return null;
}

return (
<a
href={`/disputes/strikes/${id}`}
target='_blank'
rel='noopener noreferrer'
className='notification__moderation-warning'
>
<Icon id='warning' icon={WarningIcon} />

<div className='notification__moderation-warning__content'>
<p>{intl.formatMessage(messages[action])}</p>
<span className='link-button'>
<FormattedMessage
id='notification.moderation-warning.learn_more'
defaultMessage='Learn more'
/>
</span>
</div>
</a>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';

import { HotKeys } from 'react-hotkeys';

import DangerousIcon from '@/material-icons/400-24px/dangerous-fill.svg?react';
import EditIcon from '@/material-icons/400-24px/edit.svg?react';
import FlagIcon from '@/material-icons/400-24px/flag-fill.svg?react';
import HomeIcon from '@/material-icons/400-24px/home-fill.svg?react';
Expand All @@ -30,6 +29,7 @@ import { WithRouterPropTypes } from 'mastodon/utils/react_router';

import FollowRequestContainer from '../containers/follow_request_container';

import { ModerationWarning } from './moderation_warning';
import { RelationshipsSeveranceEvent } from './relationships_severance_event';
import Report from './report';

Expand All @@ -44,18 +44,10 @@ const messages = defineMessages({
listStatus: { id: 'notification.list_status', defaultMessage: '{name} post is added on {listName}' },
statusReference: { id: 'notification.status_reference', defaultMessage: '{name} refered your post' },
update: { id: 'notification.update', defaultMessage: '{name} edited a post' },
warning: { id: 'notification.warning', defaultMessage: 'You have been warned and did something. Check your mailbox' },
warning_none: { id: 'notification.warning.none', defaultMessage: 'You have been warned. Check your mailbox.' },
warning_disable: { id: 'notification.warning.disable', defaultMessage: 'You have been warned and disabled account. Check your mailbox.' },
warning_force_cw: { id: 'notification.warning.force_cw', defaultMessage: 'You have been warned and one or more statuses have been added warning messages. Check your mailbox.' },
warning_mark_statuses_as_sensitive: { id: 'notification.warning.mark_statuses_as_sensitive', defaultMessage: 'You have been warned and some statuses have been marked as sensitive. Check your mailbox.' },
warning_delete_statuses: { id: 'notification.warning.delete_statuses', defaultMessage: 'You have been warned and one or more statuses have been deleted. Check your mailbox.' },
warning_sensitive: { id: 'notification.warning.sensitive', defaultMessage: 'You have been warned and your account has been marked as sensitive. Check your mailbox.' },
warning_silence: { id: 'notification.warning.silence', defaultMessage: 'You have been warned and your account has been silenced. Check your mailbox.' },
warning_suspend: { id: 'notification.warning.suspend', defaultMessage: 'You have been warned and your account has been suspended. Check your mailbox.' },
adminSignUp: { id: 'notification.admin.sign_up', defaultMessage: '{name} signed up' },
adminReport: { id: 'notification.admin.report', defaultMessage: '{name} reported {target}' },
relationshipsSevered: { id: 'notification.relationships_severance_event', defaultMessage: 'Lost connections with {name}' },
moderationWarning: { id: 'notification.moderation_warning', defaultMessage: 'Your have received a moderation warning' },
});

const notificationForScreenReader = (intl, message, timestamp) => {
Expand Down Expand Up @@ -482,47 +474,6 @@ class Notification extends ImmutablePureComponent {
</HotKeys>
);
}

renderWarning (notification) {
const { intl, unread } = this.props;

const preMessageKey = `warning_${notification.getIn(['account_warning', 'action'])}`;
const messageKey = Object.keys(messages).includes(preMessageKey) ? preMessageKey : 'warning';
const text = notification.getIn(['account_warning', 'text']);

return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-warning focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.statusReference, { name: notification.getIn(['account', 'acct']) }), notification.get('created_at'))}>
<div className='notification__message'>
<div className='notification__favourite-icon-wrapper'>
<Icon id='exclamation-triangle' icon={DangerousIcon} className='star-icon' fixedWidth />
</div>

<span title={notification.get('created_at')}>
{intl.formatMessage(messages[messageKey])}
</span>
</div>

{text && <div className='notification__warning-text'>{text}</div>}

{notification.get('statuses').map((status_id) => (
<StatusContainer
key={status_id}
id={status_id}
muted
withDismiss
hidden={!!this.props.hidden}
getScrollPosition={this.props.getScrollPosition}
updateScrollBottom={this.props.updateScrollBottom}
cachedMediaWidth={this.props.cachedMediaWidth}
cacheMediaWidth={this.props.cacheMediaWidth}
withoutEmojiReactions
/>
))}
</div>
</HotKeys>
);
}

renderRelationshipsSevered (notification) {
const { intl, unread, hidden } = this.props;
Expand All @@ -547,6 +498,27 @@ class Notification extends ImmutablePureComponent {
);
}

renderModerationWarning (notification) {
const { intl, unread, hidden } = this.props;
const warning = notification.get('moderation_warning');

if (!warning) {
return null;
}

return (
<HotKeys handlers={this.getHandlers()}>
<div className={classNames('notification notification-moderation-warning focusable', { unread })} tabIndex={0} aria-label={notificationForScreenReader(intl, intl.formatMessage(messages.moderationWarning), notification.get('created_at'))}>
<ModerationWarning
action={warning.get('action')}
id={warning.get('id')}
hidden={hidden}
/>
</div>
</HotKeys>
);
}

renderAdminSignUp (notification, account, link) {
const { intl, unread } = this.props;

Expand Down Expand Up @@ -626,10 +598,10 @@ class Notification extends ImmutablePureComponent {
return this.renderUpdate(notification, link);
case 'poll':
return this.renderPoll(notification, account);
case 'warning':
return this.renderWarning(notification);
case 'severed_relationships':
return this.renderRelationshipsSevered(notification);
case 'moderation_warning':
return this.renderModerationWarning(notification);
case 'admin.sign_up':
return this.renderAdminSignUp(notification, account, link);
case 'admin.report':
Expand Down
Loading

0 comments on commit 081a9f5

Please sign in to comment.