diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d0c59776..48fb6ddc0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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. diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx index 368dce1b15..61978f16e1 100644 --- a/app/soapbox/features/account/components/header.tsx +++ b/app/soapbox/features/account/components/header.tsx @@ -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 = ({ 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 = ({ 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 = ({ 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 = ({ 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 = ({ 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( = ({ account }) => { title={} />, ); - } else if (ownAccount?.id !== account.id && account.relationship?.blocking) { + } else if (ownAccount.id !== account.id && account.relationship?.blocking) { info.push( = ({ account }) => { ); } - if (ownAccount?.id !== account.id && account.relationship?.muting) { + if (ownAccount.id !== account.id && account.relationship?.muting) { info.push( = ({ account }) => { title={} />, ); - } else if (ownAccount?.id !== account.id && account.relationship?.domain_blocking) { + } else if (ownAccount.id !== account.id && account.relationship?.domain_blocking) { info.push( = ({ account }) => { {renderMessageButton()} {renderShareButton()} - {ownAccount && ( + {menu.length > 0 && ( = ({ 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 = ({ account, actionType, small }) =
-