diff --git a/app/javascript/mastodon/components/hashtag.js b/app/javascript/mastodon/components/hashtag.js index 4a5a4bb57a..8dd27290a9 100644 --- a/app/javascript/mastodon/components/hashtag.js +++ b/app/javascript/mastodon/components/hashtag.js @@ -65,23 +65,35 @@ ImmutableHashtag.propTypes = { hashtag: ImmutablePropTypes.map.isRequired, }; -const Hashtag = ({ name, href, to, people, history, className }) => ( +const Hashtag = ({ name, href, to, people, uses, history, className, description, withGraph }) => (
{name ? #{name} : } - {typeof people !== 'undefined' ? : } + {description ? ( + {description} + ) : ( + typeof people !== 'undefined' ? : + )}
-
- - 0)}> - - - -
+ {typeof uses !== 'undefined' && ( +
+ +
+ )} + + {withGraph && ( +
+ + 0)}> + + + +
+ )}
); @@ -90,9 +102,15 @@ Hashtag.propTypes = { href: PropTypes.string, to: PropTypes.string, people: PropTypes.number, + description: PropTypes.node, uses: PropTypes.number, history: PropTypes.arrayOf(PropTypes.number), className: PropTypes.string, + withGraph: PropTypes.bool, +}; + +Hashtag.defaultProps = { + withGraph: true, }; export default Hashtag; diff --git a/app/javascript/mastodon/components/navigation_portal.js b/app/javascript/mastodon/components/navigation_portal.js new file mode 100644 index 0000000000..b2d054a3b6 --- /dev/null +++ b/app/javascript/mastodon/components/navigation_portal.js @@ -0,0 +1,30 @@ +import React from 'react'; +import { Switch, Route, withRouter } from 'react-router-dom'; +import { showTrends } from 'mastodon/initial_state'; +import Trends from 'mastodon/features/getting_started/containers/trends_container'; +import AccountNavigation from 'mastodon/features/account/navigation'; + +const DefaultNavigation = () => ( + <> + {showTrends && ( + <> +
+ + + )} + +); + +export default @withRouter +class NavigationPortal extends React.PureComponent { + + render () { + return ( + + + + + ); + } + +} diff --git a/app/javascript/mastodon/features/account/components/featured_tags.js b/app/javascript/mastodon/features/account/components/featured_tags.js index 3d5b8b0793..5837f6e6d8 100644 --- a/app/javascript/mastodon/features/account/components/featured_tags.js +++ b/app/javascript/mastodon/features/account/components/featured_tags.js @@ -1,27 +1,16 @@ import React from 'react'; -import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl } from 'react-intl'; -import classNames from 'classnames'; -import Permalink from 'mastodon/components/permalink'; -import ShortNumber from 'mastodon/components/short_number'; -import { List as ImmutableList } from 'immutable'; +import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import Hashtag from 'mastodon/components/hashtag'; const messages = defineMessages({ - hashtag_all: { id: 'account.hashtag_all', defaultMessage: 'All' }, - hashtag_all_description: { id: 'account.hashtag_all_description', defaultMessage: 'All posts (deselect hashtags)' }, - hashtag_select_description: { id: 'account.hashtag_select_description', defaultMessage: 'Select hashtag #{name}' }, - statuses_counter: { id: 'account.statuses_counter', defaultMessage: '{count, plural, one {{counter} Post} other {{counter} Posts}}' }, + lastStatusAt: { id: 'account.featured_tags.last_status_at', defaultMessage: 'Last post on {date}' }, + empty: { id: 'account.featured_tags.last_status_never', defaultMessage: 'No posts' }, }); -const mapStateToProps = (state, { account }) => ({ - featuredTags: state.getIn(['user_lists', 'featured_tags', account.get('id'), 'items'], ImmutableList()), -}); - -export default @connect(mapStateToProps) -@injectIntl +export default @injectIntl class FeaturedTags extends ImmutablePureComponent { static contextTypes = { @@ -36,34 +25,27 @@ class FeaturedTags extends ImmutablePureComponent { }; render () { - const { account, featuredTags, tagged, intl } = this.props; + const { account, featuredTags, intl } = this.props; - if (!account || featuredTags.isEmpty()) { + if (!account || account.get('suspended') || featuredTags.isEmpty()) { return null; } - const suspended = account.get('suspended'); - return ( -
-
-
- {intl.formatMessage(messages.hashtag_all)} - {!suspended && featuredTags.map(featuredTag => { - const name = featuredTag.get('name'); - const url = featuredTag.get('url'); - const to = `/@${account.get('acct')}/tagged/${name}`; - const desc = intl.formatMessage(messages.hashtag_select_description, { name }); - const count = featuredTag.get('statuses_count'); +
+

}} />

- return ( - - #{name} ({}) - - ); - })} -
-
+ {featuredTags.map(featuredTag => ( + 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)} + /> + ))}
); } diff --git a/app/javascript/mastodon/features/account/containers/featured_tags_container.js b/app/javascript/mastodon/features/account/containers/featured_tags_container.js new file mode 100644 index 0000000000..7e206567fb --- /dev/null +++ b/app/javascript/mastodon/features/account/containers/featured_tags_container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import FeaturedTags from '../components/featured_tags'; +import { makeGetAccount } from 'mastodon/selectors'; +import { List as ImmutableList } from 'immutable'; + +const mapStateToProps = () => { + const getAccount = makeGetAccount(); + + return (state, { accountId }) => ({ + account: getAccount(state, accountId), + featuredTags: state.getIn(['user_lists', 'featured_tags', accountId, 'items'], ImmutableList()), + }); +}; + +export default connect(mapStateToProps)(FeaturedTags); diff --git a/app/javascript/mastodon/features/account/navigation.js b/app/javascript/mastodon/features/account/navigation.js new file mode 100644 index 0000000000..1226741395 --- /dev/null +++ b/app/javascript/mastodon/features/account/navigation.js @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import FeaturedTags from 'mastodon/features/account/containers/featured_tags_container'; + +const mapStateToProps = (state, { match: { params: { acct } } }) => { + const accountId = state.getIn(['accounts_map', acct]); + + if (!accountId) { + return { + isLoading: true, + }; + } + + return { + accountId, + isLoading: false, + }; +}; + +export default @connect(mapStateToProps) +class AccountNavigation extends React.PureComponent { + + static propTypes = { + match: PropTypes.shape({ + params: PropTypes.shape({ + acct: PropTypes.string, + tagged: PropTypes.string, + }).isRequired, + }).isRequired, + + accountId: PropTypes.string, + isLoading: PropTypes.bool, + }; + + render () { + const { accountId, isLoading, match: { params: { tagged } } } = this.props; + + if (isLoading) { + return null; + } + + return ( + <> +
+ + + ); + } + +} diff --git a/app/javascript/mastodon/features/account_timeline/components/header.js b/app/javascript/mastodon/features/account_timeline/components/header.js index ea34a934a7..f31848f412 100644 --- a/app/javascript/mastodon/features/account_timeline/components/header.js +++ b/app/javascript/mastodon/features/account_timeline/components/header.js @@ -1,8 +1,7 @@ -import React, { Fragment } from 'react'; +import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import InnerHeader from '../../account/components/header'; -import FeaturedTags from '../../account/components/featured_tags'; import ImmutablePureComponent from 'react-immutable-pure-component'; import MovedNote from './moved_note'; import { FormattedMessage } from 'react-intl'; @@ -28,7 +27,6 @@ export default class Header extends ImmutablePureComponent { hideTabs: PropTypes.bool, domain: PropTypes.string.isRequired, hidden: PropTypes.bool, - tagged: PropTypes.string, }; static contextTypes = { @@ -104,7 +102,7 @@ export default class Header extends ImmutablePureComponent { } render () { - const { account, hidden, hideTabs, tagged } = this.props; + const { account, hidden, hideTabs } = this.props; if (account === null) { return null; @@ -136,15 +134,11 @@ export default class Header extends ImmutablePureComponent { /> {!(hideTabs || hidden) && ( - -
- - - -
- - -
+
+ + + +
)}
); diff --git a/app/javascript/mastodon/features/ui/components/navigation_panel.js b/app/javascript/mastodon/features/ui/components/navigation_panel.js index 166d3552bc..757ef54ae2 100644 --- a/app/javascript/mastodon/features/ui/components/navigation_panel.js +++ b/app/javascript/mastodon/features/ui/components/navigation_panel.js @@ -3,13 +3,13 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import Logo from 'mastodon/components/logo'; -import TrendsContainer from 'mastodon/features/getting_started/containers/trends_container'; -import { showTrends, timelinePreview } from 'mastodon/initial_state'; +import { timelinePreview } from 'mastodon/initial_state'; import ColumnLink from './column_link'; import FollowRequestsColumnLink from './follow_requests_column_link'; import ListPanel from './list_panel'; import NotificationsCounterIcon from './notifications_counter_icon'; import SignInBanner from './sign_in_banner'; +import NavigationPortal from 'mastodon/components/navigation_portal'; const messages = defineMessages({ home: { id: 'tabs_bar.home', defaultMessage: 'Home' }, @@ -93,12 +93,7 @@ class NavigationPanel extends React.Component {
- {showTrends && ( - -
- - - )} +
); } diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index f8f9200f47..587eba6633 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -7338,33 +7338,6 @@ noscript { } } } - - &__hashtag-links { - overflow: hidden; - padding: 10px 5px; - margin: 0; - color: $darker-text-color; - border-bottom: 1px solid lighten($ui-base-color, 12%); - - a { - display: inline-block; - color: $darker-text-color; - text-decoration: none; - padding: 5px 10px; - font-weight: 500; - - strong { - font-weight: 700; - color: $primary-text-color; - } - } - - a.active { - color: darken($ui-base-color, 4%); - background: $darker-text-color; - border-radius: 18px; - } - } } &__account-note { @@ -7482,12 +7455,6 @@ noscript { margin-left: 5px; color: $secondary-text-color; text-decoration: none; - - &__asterisk { - color: $darker-text-color; - font-size: 18px; - vertical-align: super; - } } &__sparkline { diff --git a/app/serializers/rest/featured_tag_serializer.rb b/app/serializers/rest/featured_tag_serializer.rb index 8abcd9b90f..c4b35ab03a 100644 --- a/app/serializers/rest/featured_tag_serializer.rb +++ b/app/serializers/rest/featured_tag_serializer.rb @@ -16,4 +16,12 @@ class REST::FeaturedTagSerializer < ActiveModel::Serializer def name object.display_name end + + def statuses_count + object.statuses_count.to_s + end + + def last_status_at + object.last_status_at&.to_date&.iso8601 + end end