diff --git a/app/soapbox/actions/accounts.js b/app/soapbox/actions/accounts.js index ec010cf7b..aacdcd106 100644 --- a/app/soapbox/actions/accounts.js +++ b/app/soapbox/actions/accounts.js @@ -57,6 +57,10 @@ export const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST'; export const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS'; export const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL'; +export const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST'; +export const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS'; +export const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL'; + export const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST'; export const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS'; export const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL'; @@ -962,6 +966,43 @@ export function unpinAccountFail(error) { }; } +export function fetchPinnedAccounts(id) { + return (dispatch, getState) => { + dispatch(fetchPinnedAccountsRequest(id)); + + api(getState).get(`/api/v1/pleroma/accounts/${id}/endorsements`).then(response => { + dispatch(importFetchedAccounts(response.data)); + dispatch(fetchPinnedAccountsSuccess(id, response.data, null)); + }).catch(error => { + dispatch(fetchPinnedAccountsFail(id, error)); + }); + }; +} + +export function fetchPinnedAccountsRequest(id) { + return { + type: PINNED_ACCOUNTS_FETCH_REQUEST, + id, + }; +} + +export function fetchPinnedAccountsSuccess(id, accounts, next) { + return { + type: PINNED_ACCOUNTS_FETCH_SUCCESS, + id, + accounts, + next, + }; +} + +export function fetchPinnedAccountsFail(id, error) { + return { + type: PINNED_ACCOUNTS_FETCH_FAIL, + id, + error, + }; +} + export function accountSearch(params, cancelToken) { return (dispatch, getState) => { dispatch({ type: ACCOUNT_SEARCH_REQUEST, params }); diff --git a/app/soapbox/features/account/components/header.js b/app/soapbox/features/account/components/header.js index 356d5a6d1..62dc3236a 100644 --- a/app/soapbox/features/account/components/header.js +++ b/app/soapbox/features/account/components/header.js @@ -281,7 +281,14 @@ class Header extends ImmutablePureComponent { }); } - // menu.push({ text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), action: this.props.onEndorseToggle }); + if (features.accountEndorsements) { + menu.push({ + text: intl.formatMessage(account.getIn(['relationship', 'endorsed']) ? messages.unendorse : messages.endorse), + action: this.props.onEndorseToggle, + icon: require('@tabler/icons/icons/user-check.svg'), + }); + } + menu.push(null); } else if (features.lists && features.unrestrictedLists) { menu.push({ diff --git a/app/soapbox/features/account_timeline/components/header.js b/app/soapbox/features/account_timeline/components/header.js index 8438e67a6..8b244dd33 100644 --- a/app/soapbox/features/account_timeline/components/header.js +++ b/app/soapbox/features/account_timeline/components/header.js @@ -22,7 +22,7 @@ export default class Header extends ImmutablePureComponent { onMute: PropTypes.func.isRequired, onBlockDomain: PropTypes.func.isRequired, onUnblockDomain: PropTypes.func.isRequired, - // onEndorseToggle: PropTypes.func.isRequired, + onEndorseToggle: PropTypes.func.isRequired, onAddToList: PropTypes.func.isRequired, username: PropTypes.string, }; @@ -83,9 +83,9 @@ export default class Header extends ImmutablePureComponent { this.props.onChat(this.props.account, this.context.router.history); } - // handleEndorseToggle = () => { - // this.props.onEndorseToggle(this.props.account); - // } + handleEndorseToggle = () => { + this.props.onEndorseToggle(this.props.account); + } handleAddToList = () => { this.props.onAddToList(this.props.account); diff --git a/app/soapbox/features/account_timeline/containers/header_container.js b/app/soapbox/features/account_timeline/containers/header_container.js index a16ac36ac..5050db874 100644 --- a/app/soapbox/features/account_timeline/containers/header_container.js +++ b/app/soapbox/features/account_timeline/containers/header_container.js @@ -24,8 +24,8 @@ import { blockAccount, unblockAccount, unmuteAccount, - // pinAccount, - // unpinAccount, + pinAccount, + unpinAccount, subscribeAccount, unsubscribeAccount, } from '../../../actions/accounts'; @@ -130,13 +130,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ } }, - // onEndorseToggle(account) { - // if (account.getIn(['relationship', 'endorsed'])) { - // dispatch(unpinAccount(account.get('id'))); - // } else { - // dispatch(pinAccount(account.get('id'))); - // } - // }, + onEndorseToggle(account) { + if (account.getIn(['relationship', 'endorsed'])) { + dispatch(unpinAccount(account.get('id'))); + } else { + dispatch(pinAccount(account.get('id'))); + } + }, onReport(account) { dispatch(initReport(account)); diff --git a/app/soapbox/features/ui/components/pinned_accounts_panel.js b/app/soapbox/features/ui/components/pinned_accounts_panel.js new file mode 100644 index 000000000..da779b160 --- /dev/null +++ b/app/soapbox/features/ui/components/pinned_accounts_panel.js @@ -0,0 +1,79 @@ +import { List as ImmutableList } from 'immutable'; +import PropTypes from 'prop-types'; +import React from 'react'; +import ImmutablePropTypes from 'react-immutable-proptypes'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { FormattedMessage, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; + +import Icon from 'soapbox/components/icon'; + +import { fetchPinnedAccounts } from '../../../actions/accounts'; +import AccountContainer from '../../../containers/account_container'; + +class PinnedAccountsPanel extends ImmutablePureComponent { + + static propTypes = { + pinned: ImmutablePropTypes.list.isRequired, + fetchPinned: PropTypes.func.isRequired, + intl: PropTypes.object.isRequired, + }; + + componentDidMount() { + this.props.fetchPinned(); + } + + render() { + const { account } = this.props; + const pinned = this.props.pinned.slice(0, this.props.limit); + + if (pinned.isEmpty()) { + return null; + } + + return ( +