diff --git a/app/soapbox/components/sidebar_menu.js b/app/soapbox/components/sidebar_menu.js index e16c62405..ea4e2ef94 100644 --- a/app/soapbox/components/sidebar_menu.js +++ b/app/soapbox/components/sidebar_menu.js @@ -41,6 +41,7 @@ const messages = defineMessages({ logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, lists: { id: 'column.lists', defaultMessage: 'Lists' }, bookmarks: { id: 'column.bookmarks', defaultMessage: 'Bookmarks' }, + profileDirectory: { id: 'column.profile_directory', defaultMessage: 'Profile directory' }, header: { id: 'tabs_bar.header', defaultMessage: 'Account Info' }, apps: { id: 'tabs_bar.apps', defaultMessage: 'Apps' }, news: { id: 'tabs_bar.news', defaultMessage: 'News' }, @@ -253,6 +254,10 @@ class SidebarMenu extends ImmutablePureComponent { {intl.formatMessage(messages.bookmarks)} } + {features.profileDirectory && + + {intl.formatMessage(messages.profileDirectory)} + }
diff --git a/app/soapbox/features/directory/components/account_card.js b/app/soapbox/features/directory/components/account_card.js index 95a8264dc..4e04aa276 100644 --- a/app/soapbox/features/directory/components/account_card.js +++ b/app/soapbox/features/directory/components/account_card.js @@ -2,29 +2,17 @@ import React from 'react'; import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; -import SoapboxPropTypes from 'soapbox/utils/soapbox_prop_types'; import { connect } from 'react-redux'; +import classNames from 'classnames'; import { makeGetAccount } from 'soapbox/selectors'; import Avatar from 'soapbox/components/avatar'; import DisplayName from 'soapbox/components/display_name'; import Permalink from 'soapbox/components/permalink'; import RelativeTimestamp from 'soapbox/components/relative_timestamp'; -import IconButton from 'soapbox/components/icon_button'; -import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; +import { FormattedMessage, injectIntl } from 'react-intl'; import { getSettings } from 'soapbox/actions/settings'; import { shortNumberFormat } from 'soapbox/utils/numbers'; -import { followAccount, unfollowAccount, blockAccount, unblockAccount, unmuteAccount } from 'soapbox/actions/accounts'; -import { openModal } from 'soapbox/actions/modal'; -import { initMuteModal } from 'soapbox/actions/mutes'; - - -const messages = defineMessages({ - follow: { id: 'account.follow', defaultMessage: 'Follow' }, - unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' }, - requested: { id: 'account.requested', defaultMessage: 'Awaiting approval' }, - unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' }, - unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, -}); +import ActionButton from 'soapbox/features/ui/components/action_button'; const makeMapStateToProps = () => { const getAccount = makeGetAccount(); @@ -32,121 +20,45 @@ const makeMapStateToProps = () => { const mapStateToProps = (state, { id }) => ({ account: getAccount(state, id), autoPlayGif: getSettings(state).get('autoPlayGif'), - me: state.get('me'), }); return mapStateToProps; }; -const mapDispatchToProps = (dispatch, { intl }) => ({ - - onFollow(account) { - dispatch((_, getState) => { - const unfollowModal = getSettings(getState()).get('unfollowModal'); - if (account.getIn(['relationship', 'following']) || account.getIn(['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.getIn(['relationship', 'blocking'])) { - dispatch(unblockAccount(account.get('id'))); - } else { - dispatch(blockAccount(account.get('id'))); - } - }, - - onMute(account) { - if (account.getIn(['relationship', 'muting'])) { - dispatch(unmuteAccount(account.get('id'))); - } else { - dispatch(initMuteModal(account)); - } - }, - -}); - export default @injectIntl -@connect(makeMapStateToProps, mapDispatchToProps) +@connect(makeMapStateToProps) class AccountCard extends ImmutablePureComponent { static propTypes = { account: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - onFollow: PropTypes.func.isRequired, - onBlock: PropTypes.func.isRequired, - onMute: PropTypes.func.isRequired, autoPlayGif: PropTypes.bool, - me: SoapboxPropTypes.me, }; - handleFollow = () => { - this.props.onFollow(this.props.account); - } - - handleBlock = () => { - this.props.onBlock(this.props.account); - } - - handleMute = () => { - this.props.onMute(this.props.account); - } - render() { - const { account, intl, me, autoPlayGif } = this.props; - - let buttons; - - if (account.get('id') !== me && account.get('relationship', null) !== null) { - const following = account.getIn(['relationship', 'following']); - const requested = account.getIn(['relationship', 'requested']); - const blocking = account.getIn(['relationship', 'blocking']); - const muting = account.getIn(['relationship', 'muting']); - - if (requested) { - buttons = ; - } else if (blocking) { - buttons = ; - } else if (muting) { - buttons = ; - } else if (!account.get('moved') || following) { - buttons = ; - } - } + const { account, autoPlayGif } = this.props; return (
+
+ +
- + - -
- {buttons} -
- {account.get('note').length > 0 && account.get('note') !== '

' && ( -
-
-
- )} +
+

') && 'empty')} + dangerouslySetInnerHTML={{ __html: account.get('note_emojified') }} + /> +
{shortNumberFormat(account.get('statuses_count'))}
diff --git a/app/soapbox/features/directory/index.js b/app/soapbox/features/directory/index.js index a28c62c9e..52209e9f1 100644 --- a/app/soapbox/features/directory/index.js +++ b/app/soapbox/features/directory/index.js @@ -3,15 +3,13 @@ import { connect } from 'react-redux'; import { defineMessages, injectIntl } from 'react-intl'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; -import Column from 'soapbox/components/column'; -import ColumnHeader from 'soapbox/components/column_header'; +import Column from 'soapbox/features/ui/components/column'; import { fetchDirectory, expandDirectory } from 'soapbox/actions/directory'; import { List as ImmutableList } from 'immutable'; import AccountCard from './components/account_card'; import RadioButton from 'soapbox/components/radio_button'; import classNames from 'classnames'; import LoadMore from 'soapbox/components/load_more'; -import { ScrollContainer } from 'react-router-scroll-4'; import { getFeatures } from 'soapbox/utils/features'; const messages = defineMessages({ @@ -33,18 +31,11 @@ export default @connect(mapStateToProps) @injectIntl class Directory extends React.PureComponent { - static contextTypes = { - router: PropTypes.object, - }; - static propTypes = { isLoading: PropTypes.bool, accountIds: ImmutablePropTypes.list.isRequired, dispatch: PropTypes.func.isRequired, - shouldUpdateScroll: PropTypes.func, - columnId: PropTypes.string, intl: PropTypes.object.isRequired, - multiColumn: PropTypes.bool, title: PropTypes.string.isRequired, params: PropTypes.shape({ order: PropTypes.string, @@ -63,10 +54,6 @@ class Directory extends React.PureComponent { local: state.local === null ? (props.params.local || false) : state.local, }); - handleHeaderClick = () => { - this.column.scrollTop(); - } - componentDidMount() { const { dispatch } = this.props; dispatch(fetchDirectory(this.getParams(this.props, this.state))); @@ -82,10 +69,6 @@ class Directory extends React.PureComponent { } } - setRef = c => { - this.column = c; - } - handleChangeOrder = e => { this.setState({ order: e.target.value }); } @@ -100,20 +83,19 @@ class Directory extends React.PureComponent { } render() { - const { isLoading, accountIds, intl, columnId, multiColumn, title, shouldUpdateScroll, features } = this.props; + const { isLoading, accountIds, intl, title, features } = this.props; const { order, local } = this.getParams(this.props, this.state); - const pinned = !!columnId; - const scrollableArea = ( -
-
-
+ return ( + +
+
{features.federating && ( -
+
@@ -125,22 +107,6 @@ class Directory extends React.PureComponent {
-
- ); - - return ( - - - - {multiColumn && !pinned ? {scrollableArea} : scrollableArea} ); } diff --git a/app/soapbox/features/ui/components/link_footer.js b/app/soapbox/features/ui/components/link_footer.js index ed89dc8dd..f85caf09a 100644 --- a/app/soapbox/features/ui/components/link_footer.js +++ b/app/soapbox/features/ui/components/link_footer.js @@ -18,6 +18,7 @@ const mapStateToProps = state => { return { account, + profileDirectory: features.profileDirectory, federating: features.federating, showAliases: features.accountAliasesAPI, importAPI: features.importAPI, @@ -35,10 +36,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ }, }); -const LinkFooter = ({ onOpenHotkeys, account, federating, showAliases, importAPI, onClickLogOut, baseURL }) => ( +const LinkFooter = ({ onOpenHotkeys, account, profileDirectory, federating, showAliases, importAPI, onClickLogOut, baseURL }) => (
    {account && <> + {profileDirectory &&
  • }
  • @@ -75,6 +77,7 @@ const LinkFooter = ({ onOpenHotkeys, account, federating, showAliases, importAPI LinkFooter.propTypes = { account: ImmutablePropTypes.map, + profileDirectory: PropTypes.bool, federating: PropTypes.bool, showAliases: PropTypes.bool, importAPI: PropTypes.bool, diff --git a/app/soapbox/utils/features.js b/app/soapbox/utils/features.js index c544e7db5..8e7a695b6 100644 --- a/app/soapbox/utils/features.js +++ b/app/soapbox/utils/features.js @@ -66,6 +66,10 @@ export const getFeatures = createSelector([ accountSubscriptions: v.software === PLEROMA && gte(v.version, '1.0.0'), unrestrictedLists: v.software === PLEROMA, accountByUsername: v.software === PLEROMA, + profileDirectory: any([ + v.software === MASTODON && gte(v.compatVersion, '3.0.0'), + features.includes('profile_directory'), + ]), }; }); diff --git a/app/styles/application.scss b/app/styles/application.scss index 50347b078..69308cd47 100644 --- a/app/styles/application.scss +++ b/app/styles/application.scss @@ -91,6 +91,7 @@ @import 'components/profile-stats'; @import 'components/progress-circle'; @import 'components/register-invite'; +@import 'components/radio-button'; @import 'components/directory'; // Holiday diff --git a/app/styles/components/directory.scss b/app/styles/components/directory.scss index 6c5d5cd89..4749eef99 100644 --- a/app/styles/components/directory.scss +++ b/app/styles/components/directory.scss @@ -1,4 +1,17 @@ .directory { + &__filter-form { + display: flex; + background: var(--foreground-color); + + &__column { + padding: 10px 15px; + } + + .radio-button { + display: block; + } + } + &__list { display: grid; grid-gap: 10px; @@ -24,11 +37,19 @@ border-radius: 10px; background: var(--foreground-color); overflow: hidden; + position: relative; + + &__action-button { + z-index: 1; + position: absolute; + top: 78px; + right: 12px; + } &__img { height: 125px; position: relative; - background: var(--foreground-color); + background: var(--brand-color--med); img { display: block; @@ -42,7 +63,7 @@ &__bar { display: flex; align-items: center; - background: var(--foreground-color); + background: var(--brand-color--med); padding: 10px; &__name { @@ -50,17 +71,13 @@ display: flex; align-items: center; text-decoration: none; + overflow: hidden; } - &__relationship { - width: 23px; - min-height: 1px; - flex: 0 0 auto; - } - - .avatar { + .account__avatar { flex: 0 0 auto; width: 48px; + min-width: 48px; height: 48px; padding-top: 2px; @@ -100,11 +117,12 @@ &__extra { background: var(--foreground-color); - padding: 15px 0; display: flex; align-items: center; + justify-content: center; .accounts-table__count { + padding: 15px 0; text-align: center; font-size: 15px; font-weight: 500; @@ -118,55 +136,33 @@ font-size: 14px; } } - } - } -} - -.filter-form { - display: flex; - background: var(--foreground-color); - - &__column { - padding: 10px 15px; - } - - .radio-button { - display: block; - } -} - -.radio-button { - font-size: 14px; - position: relative; - display: inline-block; - padding: 6px 0; - line-height: 18px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; - - input[type=radio], - input[type=checkbox] { - display: none; - } - - &__input { - display: inline-block; - position: relative; - border: 1px solid var(--primary-text-color--faint); - box-sizing: border-box; - width: 18px; - height: 18px; - flex: 0 0 auto; - margin-right: 10px; - top: -1px; - border-radius: 50%; - vertical-align: middle; - - &.checked { - border-color: var(--brand-color); - background: var(--brand-color); + + .account__header__content { + box-sizing: border-box; + padding: 15px 10px; + border-bottom: 1px solid var(--brand-color--med); + width: 100%; + min-height: 50px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &.empty { + border-color: transparent; + } + + p { + display: none; + + &:first-child { + display: inline; + } + } + + br { + display: none; + } + } } } } diff --git a/app/styles/components/radio-button.scss b/app/styles/components/radio-button.scss new file mode 100644 index 000000000..d1a303f3f --- /dev/null +++ b/app/styles/components/radio-button.scss @@ -0,0 +1,35 @@ +.radio-button { + font-size: 14px; + position: relative; + display: inline-block; + padding: 6px 0; + line-height: 18px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; + + input[type=radio], + input[type=checkbox] { + display: none; + } + + &__input { + display: inline-block; + position: relative; + border: 1px solid var(--primary-text-color--faint); + box-sizing: border-box; + width: 18px; + height: 18px; + flex: 0 0 auto; + margin-right: 10px; + top: -1px; + border-radius: 50%; + vertical-align: middle; + + &.checked { + border-color: var(--brand-color); + background: var(--brand-color); + } + } +} diff --git a/app/styles/components/sidebar-menu.scss b/app/styles/components/sidebar-menu.scss index b69e22db6..10486f6bf 100644 --- a/app/styles/components/sidebar-menu.scss +++ b/app/styles/components/sidebar-menu.scss @@ -155,7 +155,7 @@ > .fa { width: 24px; - font-size: 20px; + font-size: 28px; margin-right: 15px; text-align: center; }