diff --git a/app/soapbox/actions/accounts.ts b/app/soapbox/actions/accounts.ts
index c3bc56557..a18367f1b 100644
--- a/app/soapbox/actions/accounts.ts
+++ b/app/soapbox/actions/accounts.ts
@@ -227,7 +227,12 @@ const fetchAccountFail = (id: string | null, error: AxiosError) => ({
skipAlert: true,
});
-const followAccount = (id: string, options = { reblogs: true }) =>
+type FollowAccountOpts = {
+ reblogs?: boolean,
+ notify?: boolean
+};
+
+const followAccount = (id: string, options?: FollowAccountOpts) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
diff --git a/app/soapbox/actions/reports.ts b/app/soapbox/actions/reports.ts
index 40b685ba4..dce162247 100644
--- a/app/soapbox/actions/reports.ts
+++ b/app/soapbox/actions/reports.ts
@@ -20,7 +20,7 @@ const REPORT_BLOCK_CHANGE = 'REPORT_BLOCK_CHANGE';
const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE';
-const initReport = (account: Account, status: Status) =>
+const initReport = (account: Account, status?: Status) =>
(dispatch: AppDispatch) => {
dispatch({
type: REPORT_INIT,
@@ -121,4 +121,4 @@ export {
changeReportForward,
changeReportBlock,
changeReportRule,
-};
\ No newline at end of file
+};
diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js
deleted file mode 100644
index aaeed52ee..000000000
--- a/app/soapbox/features/account/components/header.js
+++ /dev/null
@@ -1,661 +0,0 @@
-'use strict';
-
-import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
-import debounce from 'lodash/debounce';
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
-import { connect } from 'react-redux';
-import { Link } from 'react-router-dom';
-
-import { openModal } from 'soapbox/actions/modals';
-import Avatar from 'soapbox/components/avatar';
-import Badge from 'soapbox/components/badge';
-import StillImage from 'soapbox/components/still_image';
-import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
-import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
-import ActionButton from 'soapbox/features/ui/components/action-button';
-import SubscriptionButton from 'soapbox/features/ui/components/subscription-button';
-import {
- isLocal,
- isRemote,
-} from 'soapbox/utils/accounts';
-import { getFeatures } from 'soapbox/utils/features';
-
-const messages = defineMessages({
- edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
- linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
- account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
- mention: { id: 'account.mention', defaultMessage: 'Mention' },
- chat: { id: 'account.chat', defaultMessage: 'Chat with @{name}' },
- direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
- unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
- block: { id: 'account.block', defaultMessage: 'Block @{name}' },
- unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
- mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
- report: { id: 'account.report', defaultMessage: 'Report @{name}' },
- share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
- media: { id: 'account.media', defaultMessage: 'Media' },
- blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
- unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
- hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide reposts from @{name}' },
- showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show reposts from @{name}' },
- preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
- follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
- blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
- domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
- 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}' },
- deleteUser: { id: 'admin.users.actions.delete_user', defaultMessage: 'Delete @{name}' },
- verifyUser: { id: 'admin.users.actions.verify_user', defaultMessage: 'Verify @{name}' },
- unverifyUser: { id: 'admin.users.actions.unverify_user', defaultMessage: 'Unverify @{name}' },
- setDonor: { id: 'admin.users.actions.set_donor', defaultMessage: 'Set @{name} as a donor' },
- removeDonor: { id: 'admin.users.actions.remove_donor', defaultMessage: 'Remove @{name} as a donor' },
- promoteToAdmin: { id: 'admin.users.actions.promote_to_admin', defaultMessage: 'Promote @{name} to an admin' },
- promoteToModerator: { id: 'admin.users.actions.promote_to_moderator', defaultMessage: 'Promote @{name} to a moderator' },
- demoteToModerator: { id: 'admin.users.actions.demote_to_moderator', defaultMessage: 'Demote @{name} to a moderator' },
- demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' },
- suggestUser: { id: 'admin.users.actions.suggest_user', defaultMessage: 'Suggest @{name}' },
- unsuggestUser: { id: 'admin.users.actions.unsuggest_user', defaultMessage: 'Unsuggest @{name}' },
- search: { id: 'account.search', defaultMessage: 'Search from @{name}' },
-});
-
-const mapStateToProps = state => {
- const me = state.get('me');
- const account = state.getIn(['accounts', me]);
- const instance = state.get('instance');
- const features = getFeatures(instance);
-
- return {
- me,
- meAccount: account,
- features,
- };
-};
-
-export default @connect(mapStateToProps)
-@injectIntl
-class Header extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.record,
- meaccount: ImmutablePropTypes.record,
- intl: PropTypes.object.isRequired,
- username: PropTypes.string,
- features: PropTypes.object,
- };
-
- state = {
- isSmallScreen: (window.innerWidth <= 895),
- }
-
- isStatusesPageActive = (match, location) => {
- if (!match) {
- return false;
- }
-
- return !location.pathname.match(/\/(followers|following|favorites|pins)\/?$/);
- }
-
- componentDidMount() {
- window.addEventListener('resize', this.handleResize, { passive: true });
- }
-
- componentWillUnmount() {
- window.removeEventListener('resize', this.handleResize);
- }
-
- handleResize = debounce(() => {
- this.setState({ isSmallScreen: (window.innerWidth <= 895) });
- }, 5, {
- trailing: true,
- });
-
- onAvatarClick = () => {
- const avatar_url = this.props.account.get('avatar');
- const avatar = ImmutableMap({
- type: 'image',
- preview_url: avatar_url,
- url: avatar_url,
- description: '',
- });
- this.props.dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar), index: 0 }));
- }
-
- handleAvatarClick = (e) => {
- if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.onAvatarClick();
- }
- }
-
- onHeaderClick = () => {
- const header_url = this.props.account.get('header');
- const header = ImmutableMap({
- type: 'image',
- preview_url: header_url,
- url: header_url,
- description: '',
- });
- this.props.dispatch(openModal('MEDIA', { media: ImmutableList.of(header), index: 0 }));
- }
-
- handleHeaderClick = (e) => {
- if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
- e.preventDefault();
- this.onHeaderClick();
- }
- }
-
- handleShare = () => {
- navigator.share({
- text: `@${this.props.account.get('acct')}`,
- url: this.props.account.get('url'),
- }).catch((e) => {
- if (e.name !== 'AbortError') console.error(e);
- });
- }
-
- makeMenu() {
- const { account, intl, me, meAccount, features } = this.props;
-
- const menu = [];
-
- if (!account || !me) {
- return [];
- }
-
- if ('share' in navigator) {
- menu.push({
- text: intl.formatMessage(messages.share, { name: account.get('username') }),
- action: this.handleShare,
- icon: require('@tabler/icons/upload.svg'),
- });
- menu.push(null);
- }
-
- if (account.get('id') === me) {
- menu.push({
- text: intl.formatMessage(messages.edit_profile),
- to: '/settings/profile',
- icon: require('@tabler/icons/user.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.preferences),
- to: '/settings',
- icon: require('@tabler/icons/settings.svg'),
- });
- // menu.push(null);
- // menu.push({
- // text: intl.formatMessage(messages.follow_requests),
- // to: '/follow_requests',
- // icon: require('@tabler/icons/user-plus.svg'),
- // });
- menu.push(null);
- menu.push({
- text: intl.formatMessage(messages.mutes),
- to: '/mutes',
- icon: require('@tabler/icons/circle-x.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.blocks),
- to: '/blocks',
- icon: require('@tabler/icons/ban.svg'),
- });
- // menu.push({
- // text: intl.formatMessage(messages.domain_blocks),
- // to: '/domain_blocks',
- // icon: require('@tabler/icons/ban.svg'),
- // });
- } else {
- menu.push({
- text: intl.formatMessage(messages.mention, { name: account.get('username') }),
- action: this.props.onMention,
- icon: require('@tabler/icons/at.svg'),
- });
-
- // if (account.getIn(['pleroma', 'accepts_chat_messages'], false) === true) {
- // menu.push({
- // text: intl.formatMessage(messages.chat, { name: account.get('username') }),
- // action: this.props.onChat,
- // icon: require('@tabler/icons/messages.svg'),
- // });
- // } else {
- // menu.push({
- // text: intl.formatMessage(messages.direct, { name: account.get('username') }),
- // action: this.props.onDirect,
- // icon: require('@tabler/icons/mail.svg'),
- // });
- // }
-
- if (account.relationship?.following) {
- if (account.relationship?.showing_reblogs) {
- menu.push({
- text: intl.formatMessage(messages.hideReblogs, { name: account.get('username') }),
- action: this.props.onReblogToggle,
- icon: require('@tabler/icons/repeat.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.showReblogs, { name: account.get('username') }),
- action: this.props.onReblogToggle,
- icon: require('@tabler/icons/repeat.svg'),
- });
- }
-
- if (features.lists) {
- menu.push({
- text: intl.formatMessage(messages.add_or_remove_from_list),
- action: this.props.onAddToList,
- icon: require('@tabler/icons/list.svg'),
- });
- }
-
- if (features.accountEndorsements) {
- menu.push({
- text: intl.formatMessage(account.relationship?.endorsed ? messages.unendorse : messages.endorse),
- action: this.props.onEndorseToggle,
- icon: require('@tabler/icons/user-check.svg'),
- });
- }
-
- menu.push(null);
- } else if (features.lists && features.unrestrictedLists) {
- menu.push({
- text: intl.formatMessage(messages.add_or_remove_from_list),
- action: this.props.onAddToList,
- icon: require('@tabler/icons/list.svg'),
- });
- }
-
- if (features.searchFromAccount) {
- menu.push({
- text: intl.formatMessage(messages.search, { name: account.get('username') }),
- action: this.props.onSearch,
- icon: require('@tabler/icons/search.svg'),
- });
- }
-
- if (features.removeFromFollowers && account.relationship?.followed_by) {
- menu.push({
- text: intl.formatMessage(messages.removeFromFollowers),
- action: this.props.onRemoveFromFollowers,
- icon: require('@tabler/icons/user-x.svg'),
- });
- }
-
- if (account.relationship?.muting) {
- menu.push({
- text: intl.formatMessage(messages.unmute, { name: account.get('username') }),
- action: this.props.onMute,
- icon: require('@tabler/icons/circle-x.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.mute, { name: account.get('username') }),
- action: this.props.onMute,
- icon: require('@tabler/icons/circle-x.svg'),
- });
- }
-
- if (account.relationship?.blocking) {
- menu.push({
- text: intl.formatMessage(messages.unblock, { name: account.get('username') }),
- action: this.props.onBlock,
- icon: require('@tabler/icons/ban.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.block, { name: account.get('username') }),
- action: this.props.onBlock,
- icon: require('@tabler/icons/ban.svg'),
- });
- }
-
- menu.push({
- text: intl.formatMessage(messages.report, { name: account.get('username') }),
- action: this.props.onReport,
- icon: require('@tabler/icons/flag.svg'),
- });
- }
-
- if (isRemote(account)) {
- const domain = account.fqn.split('@')[1];
-
- menu.push(null);
-
- if (account.relationship?.domain_blocking) {
- menu.push({
- text: intl.formatMessage(messages.unblockDomain, { domain }),
- action: this.props.onUnblockDomain,
- icon: require('@tabler/icons/ban.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.blockDomain, { domain }),
- action: this.props.onBlockDomain,
- icon: require('@tabler/icons/ban.svg'),
- });
- }
- }
-
- if (meAccount.staff) {
- menu.push(null);
-
- if (meAccount.admin) {
- menu.push({
- text: intl.formatMessage(messages.admin_account, { name: account.get('username') }),
- to: `/pleroma/admin/#/users/${account.id}/`,
- newTab: true,
- icon: require('@tabler/icons/gavel.svg'),
- });
- }
-
- if (account.id !== me && isLocal(account) && meAccount.admin) {
- if (account.admin) {
- menu.push({
- text: intl.formatMessage(messages.demoteToModerator, { name: account.get('username') }),
- action: this.props.onPromoteToModerator,
- icon: require('@tabler/icons/arrow-up-circle.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.demoteToUser, { name: account.get('username') }),
- action: this.props.onDemoteToUser,
- icon: require('@tabler/icons/arrow-down-circle.svg'),
- });
- } else if (account.moderator) {
- menu.push({
- text: intl.formatMessage(messages.promoteToAdmin, { name: account.get('username') }),
- action: this.props.onPromoteToAdmin,
- icon: require('@tabler/icons/arrow-up-circle.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.demoteToUser, { name: account.get('username') }),
- action: this.props.onDemoteToUser,
- icon: require('@tabler/icons/arrow-down-circle.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.promoteToAdmin, { name: account.get('username') }),
- action: this.props.onPromoteToAdmin,
- icon: require('@tabler/icons/arrow-up-circle.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.promoteToModerator, { name: account.get('username') }),
- action: this.props.onPromoteToModerator,
- icon: require('@tabler/icons/arrow-up-circle.svg'),
- });
- }
- }
-
- if (account.verified) {
- menu.push({
- text: intl.formatMessage(messages.unverifyUser, { name: account.username }),
- action: this.props.onUnverifyUser,
- icon: require('@tabler/icons/check.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.verifyUser, { name: account.username }),
- action: this.props.onVerifyUser,
- icon: require('@tabler/icons/check.svg'),
- });
- }
-
- if (account.donor) {
- menu.push({
- text: intl.formatMessage(messages.removeDonor, { name: account.username }),
- action: this.props.onRemoveDonor,
- icon: require('@tabler/icons/coin.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.setDonor, { name: account.username }),
- action: this.props.onSetDonor,
- icon: require('@tabler/icons/coin.svg'),
- });
- }
-
- if (features.suggestionsV2 && meAccount.admin) {
- if (account.getIn(['pleroma', 'is_suggested'])) {
- menu.push({
- text: intl.formatMessage(messages.unsuggestUser, { name: account.get('username') }),
- action: this.props.onUnsuggestUser,
- icon: require('@tabler/icons/user-x.svg'),
- });
- } else {
- menu.push({
- text: intl.formatMessage(messages.suggestUser, { name: account.get('username') }),
- action: this.props.onSuggestUser,
- icon: require('@tabler/icons/user-check.svg'),
- });
- }
- }
-
- if (account.get('id') !== me) {
- menu.push({
- text: intl.formatMessage(messages.deactivateUser, { name: account.get('username') }),
- action: this.props.onDeactivateUser,
- icon: require('@tabler/icons/user-off.svg'),
- });
- menu.push({
- text: intl.formatMessage(messages.deleteUser, { name: account.get('username') }),
- icon: require('@tabler/icons/user-minus.svg'),
- });
- }
- }
-
- return menu;
- }
-
- makeInfo() {
- const { account, me } = this.props;
-
- const info = [];
-
- if (!account || !me) return info;
-
- if (me !== account.get('id') && account.relationship?.followed_by) {
- info.push(
- }
- />,
- );
- } else if (me !== account.get('id') && account.relationship?.blocking) {
- info.push(
- }
- />,
- );
- }
-
- if (me !== account.get('id') && account.relationship?.muting) {
- info.push(
- }
- />,
- );
- } else if (me !== account.get('id') && account.relationship?.domain_blocking) {
- info.push(
- }
- />,
- );
- }
-
- return info;
- }
-
- renderMessageButton() {
- const { intl, account, me } = this.props;
-
- if (!me || !account || account.get('id') === me) {
- return null;
- }
-
- const canChat = account.getIn(['pleroma', 'accepts_chat_messages'], false) === true;
-
- if (canChat) {
- return (
-
- );
- } else {
- return (
-
- );
- }
- }
-
- renderShareButton() {
- const { intl, account, me } = this.props;
- const canShare = 'share' in navigator;
-
- if (!(account && me && account.get('id') === me && canShare)) {
- return null;
- }
-
- return (
-
- );
- }
-
- render() {
- const { account, me } = this.props;
-
- if (!account) {
- return (
-
- );
- }
-
- const info = this.makeInfo();
- const menu = this.makeMenu();
- const header = account.get('header', '');
-
- return (
-
-
-
- {header && (
-
-
-
- )}
-
-
-
- {info}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {me && (
-
- )}
-
- {this.renderShareButton()}
- {/* {this.renderMessageButton()} */}
-
-
-
-
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx
new file mode 100644
index 000000000..559ec6ac5
--- /dev/null
+++ b/app/soapbox/features/account/components/header.tsx
@@ -0,0 +1,810 @@
+'use strict';
+
+import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
+import React from 'react';
+import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
+import { Link, useHistory } from 'react-router-dom';
+
+import { blockAccount, followAccount, pinAccount, removeFromFollowers, unblockAccount, unmuteAccount, unpinAccount, unsubscribeAccount } from 'soapbox/actions/accounts';
+import { verifyUser, unverifyUser, setDonor, removeDonor, promoteToAdmin, promoteToModerator, demoteToUser, suggestUsers, unsuggestUsers } from 'soapbox/actions/admin';
+import { launchChat } from 'soapbox/actions/chats';
+import { mentionCompose, directCompose } from 'soapbox/actions/compose';
+import { blockDomain, unblockDomain } from 'soapbox/actions/domain_blocks';
+import { openModal } from 'soapbox/actions/modals';
+import { deactivateUserModal } from 'soapbox/actions/moderation';
+import { initMuteModal } from 'soapbox/actions/mutes';
+import { initReport } from 'soapbox/actions/reports';
+import { setSearchAccount } from 'soapbox/actions/search';
+import { getSettings } from 'soapbox/actions/settings';
+import snackbar from 'soapbox/actions/snackbar';
+import Avatar from 'soapbox/components/avatar';
+import Badge from 'soapbox/components/badge';
+import StillImage from 'soapbox/components/still_image';
+import { HStack, IconButton, Menu, MenuButton, MenuItem, MenuList, MenuLink, MenuDivider } from 'soapbox/components/ui';
+import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
+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 { Account } from 'soapbox/types/entities';
+import {
+ isLocal,
+ isRemote,
+} from 'soapbox/utils/accounts';
+
+import type { Menu as MenuType } from 'soapbox/components/dropdown_menu';
+
+const messages = defineMessages({
+ edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
+ linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
+ account_locked: { id: 'account.locked_info', defaultMessage: 'This account privacy status is set to locked. The owner manually reviews who can follow them.' },
+ mention: { id: 'account.mention', defaultMessage: 'Mention' },
+ chat: { id: 'account.chat', defaultMessage: 'Chat with @{name}' },
+ direct: { id: 'account.direct', defaultMessage: 'Direct message @{name}' },
+ unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
+ block: { id: 'account.block', defaultMessage: 'Block @{name}' },
+ unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
+ mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
+ report: { id: 'account.report', defaultMessage: 'Report @{name}' },
+ share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
+ media: { id: 'account.media', defaultMessage: 'Media' },
+ blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
+ unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
+ hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide reposts from @{name}' },
+ showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show reposts from @{name}' },
+ preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
+ follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
+ blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
+ domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
+ 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}' },
+ deleteUser: { id: 'admin.users.actions.delete_user', defaultMessage: 'Delete @{name}' },
+ verifyUser: { id: 'admin.users.actions.verify_user', defaultMessage: 'Verify @{name}' },
+ unverifyUser: { id: 'admin.users.actions.unverify_user', defaultMessage: 'Unverify @{name}' },
+ setDonor: { id: 'admin.users.actions.set_donor', defaultMessage: 'Set @{name} as a donor' },
+ removeDonor: { id: 'admin.users.actions.remove_donor', defaultMessage: 'Remove @{name} as a donor' },
+ promoteToAdmin: { id: 'admin.users.actions.promote_to_admin', defaultMessage: 'Promote @{name} to an admin' },
+ promoteToModerator: { id: 'admin.users.actions.promote_to_moderator', defaultMessage: 'Promote @{name} to a moderator' },
+ demoteToModerator: { id: 'admin.users.actions.demote_to_moderator', defaultMessage: 'Demote @{name} to a moderator' },
+ demoteToUser: { id: 'admin.users.actions.demote_to_user', defaultMessage: 'Demote @{name} to a regular user' },
+ suggestUser: { id: 'admin.users.actions.suggest_user', defaultMessage: 'Suggest @{name}' },
+ unsuggestUser: { id: 'admin.users.actions.unsuggest_user', defaultMessage: 'Unsuggest @{name}' },
+ search: { id: 'account.search', defaultMessage: 'Search from @{name}' },
+ unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
+ blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
+ blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
+ blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
+ userVerified: { id: 'admin.users.user_verified_message', defaultMessage: '@{acct} was verified' },
+ userUnverified: { id: 'admin.users.user_unverified_message', defaultMessage: '@{acct} was unverified' },
+ setDonorSuccess: { id: 'admin.users.set_donor_message', defaultMessage: '@{acct} was set as a donor' },
+ removeDonorSuccess: { id: 'admin.users.remove_donor_message', defaultMessage: '@{acct} was removed as a donor' },
+ promotedToAdmin: { id: 'admin.users.actions.promote_to_admin_message', defaultMessage: '@{acct} was promoted to an admin' },
+ promotedToModerator: { id: 'admin.users.actions.promote_to_moderator_message', defaultMessage: '@{acct} was promoted to a moderator' },
+ demotedToModerator: { id: 'admin.users.actions.demote_to_moderator_message', defaultMessage: '@{acct} was demoted to a moderator' },
+ 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' },
+ userEndorsed: { id: 'account.endorse.success', defaultMessage: 'You are now featuring @{acct} on your profile' },
+ userUnendorsed: { id: 'account.unendorse.success', defaultMessage: 'You are no longer featuring @{acct}' },
+
+});
+
+interface IHeader {
+ account: Account,
+}
+
+const Header: React.FC = ({ account }) => {
+ const intl = useIntl();
+ const history = useHistory();
+ const dispatch = useAppDispatch();
+
+ const features = useFeatures();
+ const ownAccount = useOwnAccount();
+
+ const onBlock = () => {
+ if (account.relationship?.blocking) {
+ dispatch(unblockAccount(account.id));
+ } else {
+ dispatch(openModal('CONFIRM', {
+ icon: require('@tabler/icons/ban.svg'),
+ heading: ,
+ message: @{account.acct} }} />,
+ confirm: intl.formatMessage(messages.blockConfirm),
+ onConfirm: () => dispatch(blockAccount(account.id)),
+ secondary: intl.formatMessage(messages.blockAndReport),
+ onSecondary: () => {
+ dispatch(blockAccount(account.id));
+ dispatch(initReport(account));
+ },
+ }));
+ }
+ };
+
+ const onMention = () => {
+ dispatch(mentionCompose(account));
+ };
+
+ const onDirect = () => {
+ dispatch(directCompose(account));
+ };
+
+ const onReblogToggle = () => {
+ if (account.relationship?.showing_reblogs) {
+ dispatch(followAccount(account.id, { reblogs: false }));
+ } else {
+ dispatch(followAccount(account.id, { reblogs: true }));
+ }
+ };
+
+ const onEndorseToggle = () => {
+ if (account.relationship?.endorsed) {
+ dispatch(unpinAccount(account.id))
+ .then(() => dispatch(snackbar.success(intl.formatMessage(messages.userUnendorsed, { acct: account.acct }))))
+ .catch(() => {});
+ } else {
+ dispatch(pinAccount(account.id))
+ .then(() => dispatch(snackbar.success(intl.formatMessage(messages.userEndorsed, { acct: account.acct }))))
+ .catch(() => {});
+ }
+ };
+
+ const onReport = () => {
+ dispatch(initReport(account));
+ };
+
+ const onMute = () => {
+ if (account.relationship?.muting) {
+ dispatch(unmuteAccount(account.id));
+ } else {
+ dispatch(initMuteModal(account));
+ }
+ };
+
+ const onBlockDomain = (domain: string) => {
+ dispatch(openModal('CONFIRM', {
+ icon: require('@tabler/icons/ban.svg'),
+ heading: ,
+ message: {domain} }} />,
+ confirm: intl.formatMessage(messages.blockDomainConfirm),
+ onConfirm: () => dispatch(blockDomain(domain)),
+ }));
+ };
+
+ const onUnblockDomain = (domain: string) => {
+ dispatch(unblockDomain(domain));
+ };
+
+ const onAddToList = () => {
+ dispatch(openModal('LIST_ADDER', {
+ accountId: account.id,
+ }));
+ };
+
+ const onChat = () => {
+ dispatch(launchChat(account.id, history));
+ };
+
+ const onDeactivateUser = () => {
+ dispatch(deactivateUserModal(intl, account.id));
+ };
+
+ const onVerifyUser = () => {
+ const message = intl.formatMessage(messages.userVerified, { acct: account.acct });
+
+ dispatch(verifyUser(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onUnverifyUser = () => {
+ const message = intl.formatMessage(messages.userUnverified, { acct: account.acct });
+
+ dispatch(unverifyUser(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onSetDonor = () => {
+ const message = intl.formatMessage(messages.setDonorSuccess, { acct: account.acct });
+
+ dispatch(setDonor(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onRemoveDonor = () => {
+ const message = intl.formatMessage(messages.removeDonorSuccess, { acct: account.acct });
+
+ dispatch(removeDonor(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onPromoteToAdmin = () => {
+ const message = intl.formatMessage(messages.promotedToAdmin, { acct: account.acct });
+
+ dispatch(promoteToAdmin(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onPromoteToModerator = () => {
+ const messageType = account.admin ? messages.demotedToModerator : messages.promotedToModerator;
+ const message = intl.formatMessage(messageType, { acct: account.acct });
+
+ dispatch(promoteToModerator(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onDemoteToUser = () => {
+ const message = intl.formatMessage(messages.demotedToUser, { acct: account.acct });
+
+ dispatch(demoteToUser(account.id))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onSuggestUser = () => {
+ const message = intl.formatMessage(messages.userSuggested, { acct: account.acct });
+
+ dispatch(suggestUsers([account.id]))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onUnsuggestUser = () => {
+ const message = intl.formatMessage(messages.userUnsuggested, { acct: account.acct });
+
+ dispatch(unsuggestUsers([account.id]))
+ .then(() => dispatch(snackbar.success(message)))
+ .catch(() => {});
+ };
+
+ const onRemoveFromFollowers = () => {
+ dispatch((_, getState) => {
+ const unfollowModal = getSettings(getState()).get('unfollowModal');
+ if (unfollowModal) {
+ dispatch(openModal('CONFIRM', {
+ message: @{account.acct} }} />,
+ confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
+ onConfirm: () => dispatch(removeFromFollowers(account.id)),
+ }));
+ } else {
+ dispatch(removeFromFollowers(account.id));
+ }
+ });
+ };
+
+ const onSearch = () => {
+ dispatch(setSearchAccount(account.id));
+ history.push('/search');
+ };
+
+ const onAvatarClick = () => {
+ const avatar_url = account.avatar;
+ const avatar = ImmutableMap({
+ type: 'image',
+ preview_url: avatar_url,
+ url: avatar_url,
+ description: '',
+ });
+ dispatch(openModal('MEDIA', { media: ImmutableList.of(avatar), index: 0 }));
+ };
+
+ const handleAvatarClick: React.MouseEventHandler = (e) => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ onAvatarClick();
+ }
+ };
+
+ const onHeaderClick = () => {
+ const header_url = account.header;
+ const header = ImmutableMap({
+ type: 'image',
+ preview_url: header_url,
+ url: header_url,
+ description: '',
+ });
+ dispatch(openModal('MEDIA', { media: ImmutableList.of(header), index: 0 }));
+ };
+
+ const handleHeaderClick: React.MouseEventHandler = (e) => {
+ if (e.button === 0 && !(e.ctrlKey || e.metaKey)) {
+ e.preventDefault();
+ onHeaderClick();
+ }
+ };
+
+ const handleShare = () => {
+ navigator.share({
+ text: `@${account.acct}`,
+ url: account.url,
+ }).catch((e) => {
+ if (e.name !== 'AbortError') console.error(e);
+ });
+ };
+
+ const makeMenu = () => {
+ const menu: MenuType = [];
+
+ if (!account || !ownAccount) {
+ return [];
+ }
+
+ if ('share' in navigator) {
+ menu.push({
+ text: intl.formatMessage(messages.share, { name: account.username }),
+ action: handleShare,
+ icon: require('@tabler/icons/upload.svg'),
+ });
+ menu.push(null);
+ }
+
+ if (account.id === ownAccount?.id) {
+ menu.push({
+ text: intl.formatMessage(messages.edit_profile),
+ to: '/settings/profile',
+ icon: require('@tabler/icons/user.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.preferences),
+ to: '/settings',
+ icon: require('@tabler/icons/settings.svg'),
+ });
+ menu.push(null);
+ menu.push({
+ text: intl.formatMessage(messages.mutes),
+ to: '/mutes',
+ icon: require('@tabler/icons/circle-x.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.blocks),
+ to: '/blocks',
+ icon: require('@tabler/icons/ban.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.mention, { name: account.username }),
+ action: onMention,
+ icon: require('@tabler/icons/at.svg'),
+ });
+
+ if (account.getIn(['pleroma', 'accepts_chat_messages']) === true) {
+ menu.push({
+ text: intl.formatMessage(messages.chat, { name: account.username }),
+ action: onChat,
+ icon: require('@tabler/icons/messages.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.direct, { name: account.username }),
+ action: onDirect,
+ icon: require('@tabler/icons/mail.svg'),
+ });
+ }
+
+ if (account.relationship?.following) {
+ if (account.relationship?.showing_reblogs) {
+ menu.push({
+ text: intl.formatMessage(messages.hideReblogs, { name: account.username }),
+ action: onReblogToggle,
+ icon: require('@tabler/icons/repeat.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.showReblogs, { name: account.username }),
+ action: onReblogToggle,
+ icon: require('@tabler/icons/repeat.svg'),
+ });
+ }
+
+ if (features.lists) {
+ menu.push({
+ text: intl.formatMessage(messages.add_or_remove_from_list),
+ action: onAddToList,
+ icon: require('@tabler/icons/list.svg'),
+ });
+ }
+
+ if (features.accountEndorsements) {
+ menu.push({
+ text: intl.formatMessage(account.relationship?.endorsed ? messages.unendorse : messages.endorse),
+ action: onEndorseToggle,
+ icon: require('@tabler/icons/user-check.svg'),
+ });
+ }
+
+ menu.push(null);
+ } else if (features.lists && features.unrestrictedLists) {
+ menu.push({
+ text: intl.formatMessage(messages.add_or_remove_from_list),
+ action: onAddToList,
+ icon: require('@tabler/icons/list.svg'),
+ });
+ }
+
+ if (features.searchFromAccount) {
+ menu.push({
+ text: intl.formatMessage(messages.search, { name: account.username }),
+ action: onSearch,
+ icon: require('@tabler/icons/search.svg'),
+ });
+ }
+
+ if (features.removeFromFollowers && account.relationship?.followed_by) {
+ menu.push({
+ text: intl.formatMessage(messages.removeFromFollowers),
+ action: onRemoveFromFollowers,
+ icon: require('@tabler/icons/user-x.svg'),
+ });
+ }
+
+ if (account.relationship?.muting) {
+ menu.push({
+ text: intl.formatMessage(messages.unmute, { name: account.username }),
+ action: onMute,
+ icon: require('@tabler/icons/circle-x.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.mute, { name: account.username }),
+ action: onMute,
+ icon: require('@tabler/icons/circle-x.svg'),
+ });
+ }
+
+ if (account.relationship?.blocking) {
+ menu.push({
+ text: intl.formatMessage(messages.unblock, { name: account.username }),
+ action: onBlock,
+ icon: require('@tabler/icons/ban.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.block, { name: account.username }),
+ action: onBlock,
+ icon: require('@tabler/icons/ban.svg'),
+ });
+ }
+
+ menu.push({
+ text: intl.formatMessage(messages.report, { name: account.username }),
+ action: onReport,
+ icon: require('@tabler/icons/flag.svg'),
+ });
+ }
+
+ if (isRemote(account)) {
+ const domain = account.fqn.split('@')[1];
+
+ menu.push(null);
+
+ if (account.relationship?.domain_blocking) {
+ menu.push({
+ text: intl.formatMessage(messages.unblockDomain, { domain }),
+ action: () => onUnblockDomain(domain),
+ icon: require('@tabler/icons/ban.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.blockDomain, { domain }),
+ action: () => onBlockDomain(domain),
+ icon: require('@tabler/icons/ban.svg'),
+ });
+ }
+ }
+
+ if (ownAccount?.staff) {
+ menu.push(null);
+
+ if (ownAccount?.admin) {
+ menu.push({
+ text: intl.formatMessage(messages.admin_account, { name: account.username }),
+ to: `/pleroma/admin/#/users/${account.id}/`,
+ newTab: true,
+ icon: require('@tabler/icons/gavel.svg'),
+ });
+ }
+
+ if (account.id !== ownAccount?.id && isLocal(account) && ownAccount.admin) {
+ if (account.admin) {
+ menu.push({
+ text: intl.formatMessage(messages.demoteToModerator, { name: account.username }),
+ action: onPromoteToModerator,
+ icon: require('@tabler/icons/arrow-up-circle.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.demoteToUser, { name: account.username }),
+ action: onDemoteToUser,
+ icon: require('@tabler/icons/arrow-down-circle.svg'),
+ });
+ } else if (account.moderator) {
+ menu.push({
+ text: intl.formatMessage(messages.promoteToAdmin, { name: account.username }),
+ action: onPromoteToAdmin,
+ icon: require('@tabler/icons/arrow-up-circle.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.demoteToUser, { name: account.username }),
+ action: onDemoteToUser,
+ icon: require('@tabler/icons/arrow-down-circle.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.promoteToAdmin, { name: account.username }),
+ action: onPromoteToAdmin,
+ icon: require('@tabler/icons/arrow-up-circle.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.promoteToModerator, { name: account.username }),
+ action: onPromoteToModerator,
+ icon: require('@tabler/icons/arrow-up-circle.svg'),
+ });
+ }
+ }
+
+ if (account.verified) {
+ menu.push({
+ text: intl.formatMessage(messages.unverifyUser, { name: account.username }),
+ action: onUnverifyUser,
+ icon: require('@tabler/icons/check.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.verifyUser, { name: account.username }),
+ action: onVerifyUser,
+ icon: require('@tabler/icons/check.svg'),
+ });
+ }
+
+ if (account.donor) {
+ menu.push({
+ text: intl.formatMessage(messages.removeDonor, { name: account.username }),
+ action: onRemoveDonor,
+ icon: require('@tabler/icons/coin.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.setDonor, { name: account.username }),
+ action: onSetDonor,
+ icon: require('@tabler/icons/coin.svg'),
+ });
+ }
+
+ if (features.suggestionsV2 && ownAccount.admin) {
+ if (account.getIn(['pleroma', 'is_suggested'])) {
+ menu.push({
+ text: intl.formatMessage(messages.unsuggestUser, { name: account.username }),
+ action: onUnsuggestUser,
+ icon: require('@tabler/icons/user-x.svg'),
+ });
+ } else {
+ menu.push({
+ text: intl.formatMessage(messages.suggestUser, { name: account.username }),
+ action: onSuggestUser,
+ icon: require('@tabler/icons/user-check.svg'),
+ });
+ }
+ }
+
+ if (account.id !== ownAccount?.id) {
+ menu.push({
+ text: intl.formatMessage(messages.deactivateUser, { name: account.username }),
+ action: onDeactivateUser,
+ icon: require('@tabler/icons/user-off.svg'),
+ });
+ menu.push({
+ text: intl.formatMessage(messages.deleteUser, { name: account.username }),
+ icon: require('@tabler/icons/user-minus.svg'),
+ });
+ }
+ }
+
+ return menu;
+ };
+
+ const makeInfo = () => {
+ const info: React.ReactNode[] = [];
+
+ if (!account || !ownAccount) return info;
+
+ if (ownAccount?.id !== account.id && account.relationship?.followed_by) {
+ info.push(
+ }
+ />,
+ );
+ } else if (ownAccount?.id !== account.id && account.relationship?.blocking) {
+ info.push(
+ }
+ />,
+ );
+ }
+
+ if (ownAccount?.id !== account.id && account.relationship?.muting) {
+ info.push(
+ }
+ />,
+ );
+ } else if (ownAccount?.id !== account.id && account.relationship?.domain_blocking) {
+ info.push(
+ }
+ />,
+ );
+ }
+
+ return info;
+ };
+
+ const renderMessageButton = () => {
+ if (!ownAccount || !account || account.id === ownAccount?.id) {
+ return null;
+ }
+
+ const canChat = account.getIn(['pleroma', 'accepts_chat_messages']) === true;
+
+ if (canChat) {
+ return (
+
+ );
+ } else {
+ return (
+
+ );
+ }
+ };
+
+ const renderShareButton = () => {
+ const canShare = 'share' in navigator;
+
+ if (!(account && ownAccount?.id && account.id === ownAccount?.id && canShare)) {
+ return null;
+ }
+
+ return (
+
+ );
+ };
+
+ if (!account) {
+ return (
+
+ );
+ }
+
+ const info = makeInfo();
+ const menu = makeMenu();
+
+ return (
+
+
+
+ {account.header && (
+
+
+
+ )}
+
+
+
+ {info}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {ownAccount && (
+
+ )}
+
+ {renderShareButton()}
+ {renderMessageButton()}
+
+
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/app/soapbox/features/account_timeline/components/header.js b/app/soapbox/features/account_timeline/components/header.js
deleted file mode 100644
index 493bd7a39..000000000
--- a/app/soapbox/features/account_timeline/components/header.js
+++ /dev/null
@@ -1,198 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import ImmutablePropTypes from 'react-immutable-proptypes';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { withRouter } from 'react-router-dom';
-
-import InnerHeader from '../../account/components/header';
-
-import MovedNote from './moved_note';
-
-export default @withRouter
-class Header extends ImmutablePureComponent {
-
- static propTypes = {
- account: ImmutablePropTypes.record,
- onFollow: PropTypes.func.isRequired,
- onBlock: PropTypes.func.isRequired,
- onMention: PropTypes.func.isRequired,
- onDirect: PropTypes.func.isRequired,
- onChat: PropTypes.func,
- onReblogToggle: PropTypes.func.isRequired,
- onReport: PropTypes.func.isRequired,
- onMute: PropTypes.func.isRequired,
- onBlockDomain: PropTypes.func.isRequired,
- onUnblockDomain: PropTypes.func.isRequired,
- onEndorseToggle: PropTypes.func.isRequired,
- onAddToList: PropTypes.func.isRequired,
- onRemoveFromFollowers: PropTypes.func.isRequired,
- onSearch: PropTypes.func.isRequired,
- username: PropTypes.string,
- history: PropTypes.object,
- };
-
- handleFollow = () => {
- this.props.onFollow(this.props.account);
- }
-
- handleBlock = () => {
- this.props.onBlock(this.props.account);
- }
-
- handleMention = () => {
- this.props.onMention(this.props.account);
- }
-
- handleDirect = () => {
- this.props.onDirect(this.props.account);
- }
-
- handleReport = () => {
- this.props.onReport(this.props.account);
- }
-
- handleReblogToggle = () => {
- this.props.onReblogToggle(this.props.account);
- }
-
- handleSubscriptionToggle = () => {
- this.props.onSubscriptionToggle(this.props.account);
- }
-
- handleNotifyToggle = () => {
- this.props.onNotifyToggle(this.props.account);
- }
-
- handleMute = () => {
- this.props.onMute(this.props.account);
- }
-
- handleBlockDomain = () => {
- const domain = this.props.account.get('acct').split('@')[1];
-
- if (!domain) return;
-
- this.props.onBlockDomain(domain);
- }
-
- handleUnblockDomain = () => {
- const domain = this.props.account.get('acct').split('@')[1];
-
- if (!domain) return;
-
- this.props.onUnblockDomain(domain);
- }
-
- handleChat = () => {
- this.props.onChat(this.props.account, this.props.history);
- }
-
- handleEndorseToggle = () => {
- this.props.onEndorseToggle(this.props.account);
- }
-
- handleAddToList = () => {
- this.props.onAddToList(this.props.account);
- }
-
- handleDeactivateUser = () => {
- this.props.onDeactivateUser(this.props.account);
- }
-
- handleDeleteUser = () => {
- this.props.onDeleteUser(this.props.account);
- }
-
- handleVerifyUser = () => {
- this.props.onVerifyUser(this.props.account);
- }
-
- handleUnverifyUser = () => {
- this.props.onUnverifyUser(this.props.account);
- }
-
- handleSetDonor = () => {
- this.props.onSetDonor(this.props.account);
- }
-
- handleRemoveDonor = () => {
- this.props.onRemoveDonor(this.props.account);
- }
-
- handlePromoteToAdmin = () => {
- this.props.onPromoteToAdmin(this.props.account);
- }
-
- handlePromoteToModerator = () => {
- this.props.onPromoteToModerator(this.props.account);
- }
-
- handleDemoteToUser = () => {
- this.props.onDemoteToUser(this.props.account);
- }
-
- handleSuggestUser = () => {
- this.props.onSuggestUser(this.props.account);
- }
-
- handleUnsuggestUser = () => {
- this.props.onUnsuggestUser(this.props.account);
- }
-
- handleShowNote = () => {
- this.props.onShowNote(this.props.account);
- }
-
- handleRemoveFromFollowers = () => {
- this.props.onRemoveFromFollowers(this.props.account);
- }
-
- handleSearch = () => {
- this.props.onSearch(this.props.account, this.props.history);
- }
-
- render() {
- const { account } = this.props;
- const moved = (account) ? account.get('moved') : false;
-
- return (
- <>
- { moved && }
-
-
- >
- );
- }
-
-}
diff --git a/app/soapbox/features/account_timeline/components/header.tsx b/app/soapbox/features/account_timeline/components/header.tsx
new file mode 100644
index 000000000..6ed8ffd83
--- /dev/null
+++ b/app/soapbox/features/account_timeline/components/header.tsx
@@ -0,0 +1,32 @@
+import React from 'react';
+
+import InnerHeader from 'soapbox/features/account/components/header';
+
+import MovedNote from './moved_note';
+
+import type { Account } from 'soapbox/types/entities';
+
+interface IHeader {
+ account?: Account,
+}
+
+/**
+ * Legacy account header middleman component that exists for no reason.
+ * @deprecated Use account Header directly.
+ */
+const Header: React.FC = ({ account }) => {
+ if (!account) {
+ return null;
+ }
+
+ return (
+ <>
+ {(account.moved && typeof account.moved === 'object') && (
+
+ )}
+
+ >
+ );
+};
+
+export default Header;
diff --git a/app/soapbox/features/account_timeline/containers/header_container.js b/app/soapbox/features/account_timeline/containers/header_container.js
deleted file mode 100644
index af113d8f2..000000000
--- a/app/soapbox/features/account_timeline/containers/header_container.js
+++ /dev/null
@@ -1,304 +0,0 @@
-import React from 'react';
-import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
-import { connect } from 'react-redux';
-
-import { initAccountNoteModal } from 'soapbox/actions/account-notes';
-import {
- followAccount,
- unfollowAccount,
- blockAccount,
- unblockAccount,
- unmuteAccount,
- pinAccount,
- unpinAccount,
- subscribeAccount,
- unsubscribeAccount,
- removeFromFollowers,
-} from 'soapbox/actions/accounts';
-import {
- verifyUser,
- unverifyUser,
- setDonor,
- removeDonor,
- promoteToAdmin,
- promoteToModerator,
- demoteToUser,
- suggestUsers,
- unsuggestUsers,
-} from 'soapbox/actions/admin';
-import { launchChat } from 'soapbox/actions/chats';
-import {
- mentionCompose,
- directCompose,
-} from 'soapbox/actions/compose';
-import { blockDomain, unblockDomain } from 'soapbox/actions/domain_blocks';
-import { openModal } from 'soapbox/actions/modals';
-import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation';
-import { initMuteModal } from 'soapbox/actions/mutes';
-import { initReport } from 'soapbox/actions/reports';
-import { setSearchAccount } from 'soapbox/actions/search';
-import { getSettings } from 'soapbox/actions/settings';
-import snackbar from 'soapbox/actions/snackbar';
-import { makeGetAccount } from 'soapbox/selectors';
-
-import Header from '../components/header';
-
-const messages = defineMessages({
- unfollowConfirm: { id: 'confirmations.unfollow.confirm', defaultMessage: 'Unfollow' },
- blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
- blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
- blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
- userVerified: { id: 'admin.users.user_verified_message', defaultMessage: '@{acct} was verified' },
- userUnverified: { id: 'admin.users.user_unverified_message', defaultMessage: '@{acct} was unverified' },
- setDonor: { id: 'admin.users.set_donor_message', defaultMessage: '@{acct} was set as a donor' },
- removeDonor: { id: 'admin.users.remove_donor_message', defaultMessage: '@{acct} was removed as a donor' },
- promotedToAdmin: { id: 'admin.users.actions.promote_to_admin_message', defaultMessage: '@{acct} was promoted to an admin' },
- promotedToModerator: { id: 'admin.users.actions.promote_to_moderator_message', defaultMessage: '@{acct} was promoted to a moderator' },
- demotedToModerator: { id: 'admin.users.actions.demote_to_moderator_message', defaultMessage: '@{acct} was demoted to a moderator' },
- 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' },
- userEndorsed: { id: 'account.endorse.success', defaultMessage: 'You are now featuring @{acct} on your profile' },
- userUnendorsed: { id: 'account.unendorse.success', defaultMessage: 'You are no longer featuring @{acct}' },
-});
-
-const makeMapStateToProps = () => {
- const getAccount = makeGetAccount();
-
- const mapStateToProps = (state, { accountId }) => ({
- account: getAccount(state, accountId),
- });
-
- return mapStateToProps;
-};
-
-const mapDispatchToProps = (dispatch, { intl }) => ({
-
- onFollow(account) {
- dispatch((_, getState) => {
- const unfollowModal = getSettings(getState()).get('unfollowModal');
- if (account.relationship?.following || account.relationship?.requested) {
- if (unfollowModal) {
- dispatch(openModal('CONFIRM', {
- message: @{account.get('acct')} }} />,
- confirm: intl.formatMessage(messages.unfollowConfirm),
- onConfirm: () => dispatch(unfollowAccount(account.get('id'))),
- }));
- } else {
- dispatch(unfollowAccount(account.get('id')));
- }
- } else {
- dispatch(followAccount(account.get('id')));
- }
- });
- },
-
- onBlock(account) {
- if (account.relationship?.blocking) {
- dispatch(unblockAccount(account.get('id')));
- } else {
- dispatch(openModal('CONFIRM', {
- icon: require('@tabler/icons/ban.svg'),
- heading: ,
- message: @{account.get('acct')} }} />,
- 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));
- },
- }));
- }
- },
-
- onMention(account) {
- dispatch(mentionCompose(account));
- },
-
- onDirect(account) {
- dispatch(directCompose(account));
- },
-
- onReblogToggle(account) {
- if (account.relationship?.showing_reblogs) {
- dispatch(followAccount(account.get('id'), { reblogs: false }));
- } else {
- dispatch(followAccount(account.get('id'), { reblogs: true }));
- }
- },
-
- onSubscriptionToggle(account) {
- if (account.relationship?.subscribing) {
- dispatch(unsubscribeAccount(account.get('id')));
- } else {
- dispatch(subscribeAccount(account.get('id')));
- }
- },
-
- onNotifyToggle(account) {
- if (account.relationship?.notifying) {
- dispatch(followAccount(account.get('id'), { notify: false }));
- } else {
- dispatch(followAccount(account.get('id'), { notify: true }));
- }
- },
-
- onEndorseToggle(account) {
- if (account.relationship?.endorsed) {
- dispatch(unpinAccount(account.get('id')))
- .then(() => dispatch(snackbar.success(intl.formatMessage(messages.userUnendorsed, { acct: account.acct }))))
- .catch(() => {});
- } else {
- dispatch(pinAccount(account.get('id')))
- .then(() => dispatch(snackbar.success(intl.formatMessage(messages.userEndorsed, { acct: account.acct }))))
- .catch(() => {});
- }
- },
-
- onReport(account) {
- dispatch(initReport(account));
- },
-
- onMute(account) {
- if (account.relationship?.muting) {
- dispatch(unmuteAccount(account.get('id')));
- } else {
- dispatch(initMuteModal(account));
- }
- },
-
- onBlockDomain(domain) {
- dispatch(openModal('CONFIRM', {
- icon: require('@tabler/icons/ban.svg'),
- heading: ,
- message: {domain} }} />,
- confirm: intl.formatMessage(messages.blockDomainConfirm),
- onConfirm: () => dispatch(blockDomain(domain)),
- }));
- },
-
- onUnblockDomain(domain) {
- dispatch(unblockDomain(domain));
- },
-
- onAddToList(account) {
- dispatch(openModal('LIST_ADDER', {
- accountId: account.get('id'),
- }));
- },
-
- onChat(account, router) {
- dispatch(launchChat(account.get('id'), router));
- },
-
- onDeactivateUser(account) {
- dispatch(deactivateUserModal(intl, account.get('id')));
- },
-
- onDeleteUser(account) {
- dispatch(deleteUserModal(intl, account.get('id')));
- },
-
- onVerifyUser(account) {
- const message = intl.formatMessage(messages.userVerified, { acct: account.get('acct') });
-
- dispatch(verifyUser(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onUnverifyUser(account) {
- const message = intl.formatMessage(messages.userUnverified, { acct: account.get('acct') });
-
- dispatch(unverifyUser(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onSetDonor(account) {
- const message = intl.formatMessage(messages.setDonor, { acct: account.get('acct') });
-
- dispatch(setDonor(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onRemoveDonor(account) {
- const message = intl.formatMessage(messages.removeDonor, { acct: account.get('acct') });
-
- dispatch(removeDonor(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onPromoteToAdmin(account) {
- const message = intl.formatMessage(messages.promotedToAdmin, { acct: account.get('acct') });
-
- dispatch(promoteToAdmin(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onPromoteToModerator(account) {
- const messageType = account.admin ? messages.demotedToModerator : messages.promotedToModerator;
- const message = intl.formatMessage(messageType, { acct: account.get('acct') });
-
- dispatch(promoteToModerator(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onDemoteToUser(account) {
- const message = intl.formatMessage(messages.demotedToUser, { acct: account.get('acct') });
-
- dispatch(demoteToUser(account.get('id')))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onSuggestUser(account) {
- const message = intl.formatMessage(messages.userSuggested, { acct: account.get('acct') });
-
- dispatch(suggestUsers([account.get('id')]))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onUnsuggestUser(account) {
- const message = intl.formatMessage(messages.userUnsuggested, { acct: account.get('acct') });
-
- dispatch(unsuggestUsers([account.get('id')]))
- .then(() => dispatch(snackbar.success(message)))
- .catch(() => {});
- },
-
- onShowNote(account) {
- dispatch(initAccountNoteModal(account));
- },
-
- onRemoveFromFollowers(account) {
- dispatch((_, getState) => {
- const unfollowModal = getSettings(getState()).get('unfollowModal');
- if (unfollowModal) {
- dispatch(openModal('CONFIRM', {
- message: @{account.get('acct')} }} />,
- confirm: intl.formatMessage(messages.removeFromFollowersConfirm),
- onConfirm: () => dispatch(removeFromFollowers(account.get('id'))),
- }));
- } else {
- dispatch(removeFromFollowers(account.get('id')));
- }
- });
- },
-
- onSearch(account, router) {
- dispatch((dispatch) => {
- dispatch(setSearchAccount(account.id));
- router.push('/search');
- });
- },
-});
-
-export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Header));
diff --git a/app/soapbox/features/account_timeline/containers/header_container.tsx b/app/soapbox/features/account_timeline/containers/header_container.tsx
new file mode 100644
index 000000000..bb303153c
--- /dev/null
+++ b/app/soapbox/features/account_timeline/containers/header_container.tsx
@@ -0,0 +1,28 @@
+import React from 'react';
+
+import { useAppSelector } from 'soapbox/hooks';
+import { makeGetAccount } from 'soapbox/selectors';
+
+import Header from '../components/header';
+
+const getAccount = makeGetAccount();
+
+interface IHeaderContainer {
+ accountId: string,
+}
+
+/**
+ * Legacy account Header container, accepting an accountId instead of an account.
+ * @deprecated Use account Header directly.
+ */
+const HeaderContainer: React.FC = ({ accountId }) => {
+ const account = useAppSelector(state => getAccount(state, accountId));
+
+ if (account) {
+ return ;
+ } else {
+ return null;
+ }
+};
+
+export default HeaderContainer;