Merge branch 'rss-button' into 'develop'

Add RSS link to account menu

See merge request soapbox-pub/soapbox!1881
environments/review-emoji-comp-a92uv0/deployments/2557
marcin mikołajczak 2 years ago
commit dbf2e53b93

@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Compatibility: improved browser support for older browsers.
- Events: allow to repost events in event menu.
- Groups: Initial support for groups.
- Profile: Add RSS link to user profiles.
### Changed
- Chats: improved display of media attachments.

@ -22,13 +22,14 @@ import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
import MovedNote from 'soapbox/features/account-timeline/components/moved-note';
import ActionButton from 'soapbox/features/ui/components/action-button';
import SubscriptionButton from 'soapbox/features/ui/components/subscription-button';
import { useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { normalizeAttachment } from 'soapbox/normalizers';
import { ChatKeys, useChats } from 'soapbox/queries/chats';
import { queryClient } from 'soapbox/queries/client';
import toast from 'soapbox/toast';
import { Account } from 'soapbox/types/entities';
import { isDefaultHeader, isRemote } from 'soapbox/utils/accounts';
import { isDefaultHeader, isLocal, isRemote } from 'soapbox/utils/accounts';
import { MASTODON, parseVersion } from 'soapbox/utils/features';
import type { Menu as MenuType } from 'soapbox/components/dropdown-menu';
@ -71,6 +72,7 @@ const messages = defineMessages({
userUnendorsed: { id: 'account.unendorse.success', defaultMessage: 'You are no longer featuring @{acct}' },
profileExternal: { id: 'account.profile_external', defaultMessage: 'View profile on {domain}' },
header: { id: 'account.header.alt', defaultMessage: 'Profile header' },
subscribeFeed: { id: 'account.rss_feed', defaultMessage: 'Subscribe to RSS feed' },
});
interface IHeader {
@ -85,6 +87,8 @@ const Header: React.FC<IHeader> = ({ account }) => {
const features = useFeatures();
const ownAccount = useOwnAccount();
const { software } = useAppSelector((state) => parseVersion(state.instance.version));
const { getOrCreateChatByAccountId } = useChats();
const createAndNavigateToChat = useMutation((accountId: string) => {
@ -257,6 +261,10 @@ const Header: React.FC<IHeader> = ({ account }) => {
}
};
const handleRssFeedClick = () => {
window.open(software === MASTODON ? `${account.url}.rss` : `${account.url}/feed.rss`, '_blank');
};
const handleShare = () => {
navigator.share({
text: `@${account.acct}`,
@ -269,20 +277,43 @@ const Header: React.FC<IHeader> = ({ account }) => {
const makeMenu = () => {
const menu: MenuType = [];
if (!account || !ownAccount) {
if (!account) {
return [];
}
if (features.rssFeeds && isLocal(account)) {
menu.push({
text: intl.formatMessage(messages.subscribeFeed),
action: handleRssFeedClick,
icon: require('@tabler/icons/rss.svg'),
});
}
if ('share' in navigator) {
menu.push({
text: intl.formatMessage(messages.share, { name: account.username }),
action: handleShare,
icon: require('@tabler/icons/upload.svg'),
});
}
if (features.federating && isRemote(account)) {
const domain = account.fqn.split('@')[1];
menu.push({
text: intl.formatMessage(messages.profileExternal, { domain }),
action: () => onProfileExternal(account.url),
icon: require('@tabler/icons/external-link.svg'),
});
}
if (!ownAccount) return menu;
if (menu.length) {
menu.push(null);
}
if (account.id === ownAccount?.id) {
if (account.id === ownAccount.id) {
menu.push({
text: intl.formatMessage(messages.edit_profile),
to: '/settings/profile',
@ -435,17 +466,9 @@ const Header: React.FC<IHeader> = ({ account }) => {
icon: require('@tabler/icons/ban.svg'),
});
}
if (features.federating) {
menu.push({
text: intl.formatMessage(messages.profileExternal, { domain }),
action: () => onProfileExternal(account.url),
icon: require('@tabler/icons/external-link.svg'),
});
}
}
if (ownAccount?.staff) {
if (ownAccount.staff) {
menu.push(null);
menu.push({
@ -463,7 +486,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
if (!account || !ownAccount) return info;
if (ownAccount?.id !== account.id && account.relationship?.followed_by) {
if (ownAccount.id !== account.id && account.relationship?.followed_by) {
info.push(
<Badge
key='followed_by'
@ -471,7 +494,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
title={<FormattedMessage id='account.follows_you' defaultMessage='Follows you' />}
/>,
);
} else if (ownAccount?.id !== account.id && account.relationship?.blocking) {
} else if (ownAccount.id !== account.id && account.relationship?.blocking) {
info.push(
<Badge
key='blocked'
@ -481,7 +504,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
);
}
if (ownAccount?.id !== account.id && account.relationship?.muting) {
if (ownAccount.id !== account.id && account.relationship?.muting) {
info.push(
<Badge
key='muted'
@ -489,7 +512,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
title={<FormattedMessage id='account.muted' defaultMessage='Muted' />}
/>,
);
} else if (ownAccount?.id !== account.id && account.relationship?.domain_blocking) {
} else if (ownAccount.id !== account.id && account.relationship?.domain_blocking) {
info.push(
<Badge
key='domain_blocked'
@ -621,7 +644,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
{renderMessageButton()}
{renderShareButton()}
{ownAccount && (
{menu.length > 0 && (
<Menu>
<MenuButton
as={IconButton}

@ -156,6 +156,7 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
onClick={handleRemoteFollow}
icon={require('@tabler/icons/plus.svg')}
text={intl.formatMessage(messages.follow)}
size='sm'
/>
);
// Pleroma's classic remote follow form.
@ -164,7 +165,11 @@ const ActionButton: React.FC<IActionButton> = ({ account, actionType, small }) =
<form method='POST' action='/main/ostatus'>
<input type='hidden' name='nickname' value={account.acct} />
<input type='hidden' name='profile' value='' />
<Button text={intl.formatMessage(messages.remote_follow)} type='submit' />
<Button
text={intl.formatMessage(messages.remote_follow)}
type='submit'
size='sm'
/>
</form>
);
}

@ -32,12 +32,12 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
const isFollowing = account.relationship?.following;
const isRequested = account.relationship?.requested;
const isSubscribed = features.accountNotifies ?
account.relationship?.notifying :
account.relationship?.subscribing;
const title = isSubscribed ?
intl.formatMessage(messages.unsubscribe, { name: account.get('username') }) :
intl.formatMessage(messages.subscribe, { name: account.get('username') });
const isSubscribed = features.accountNotifies
? account.relationship?.notifying
: account.relationship?.subscribing;
const title = isSubscribed
? intl.formatMessage(messages.unsubscribe, { name: account.get('username') })
: intl.formatMessage(messages.subscribe, { name: account.get('username') });
const onSubscribeSuccess = () =>
toast.success(intl.formatMessage(messages.subscribeSuccess));

@ -47,6 +47,7 @@
"account.report": "Report @{name}",
"account.requested": "Awaiting approval. Click to cancel follow request",
"account.requested_small": "Awaiting approval",
"account.rss_feed": "Subscribe to RSS feed",
"account.search": "Search from @{name}",
"account.search_self": "Search your posts",
"account.share": "Share @{name}'s profile",

@ -47,6 +47,7 @@
"account.report": "Zgłoś @{name}",
"account.requested": "Oczekująca prośba, kliknij aby anulować",
"account.requested_small": "Oczekująca prośba",
"account.rss_feed": "Subskrybuj kanał RSS",
"account.search": "Szukaj wpisów @{name}",
"account.search_self": "Szukaj własnych wpisów",
"account.share": "Udostępnij profil @{name}",

@ -711,6 +711,14 @@ const getInstanceFeatures = (instance: Instance) => {
v.software === PLEROMA,
]),
/**
* Ability to follow account feeds using RSS.
*/
rssFeeds: any([
v.software === MASTODON,
v.software === PLEROMA,
]),
/**
* Can schedule statuses to be posted at a later time.
* @see POST /api/v1/statuses

@ -140,7 +140,7 @@ const configuration: Configuration = {
'/report.html',
];
if (backendRoutes.some(path => pathname.startsWith(path)) || pathname.endsWith('/embed')) {
if (backendRoutes.some(path => pathname.startsWith(path)) || pathname.endsWith('/embed') || pathname.endsWith('.rss')) {
return url;
}
},

Loading…
Cancel
Save