diff --git a/app/javascript/mastodon/components/hashtag_bar.jsx b/app/javascript/mastodon/components/hashtag_bar.jsx new file mode 100644 index 0000000000..6a39005e16 --- /dev/null +++ b/app/javascript/mastodon/components/hashtag_bar.jsx @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import { useMemo, useState, useCallback } from 'react'; + +import { FormattedMessage } from 'react-intl'; + +import { Link } from 'react-router-dom'; + +import ImmutablePropTypes from 'react-immutable-proptypes'; + +const domParser = new DOMParser(); + +// About two lines on desktop +const VISIBLE_HASHTAGS = 7; + +export const HashtagBar = ({ hashtags, text }) => { + const renderedHashtags = useMemo(() => { + const body = domParser.parseFromString(text, 'text/html').documentElement; + return [].map.call(body.querySelectorAll('[rel=tag]'), node => node.textContent.toLowerCase()); + }, [text]); + + const invisibleHashtags = useMemo(() => ( + hashtags.filter(hashtag => !renderedHashtags.some(textContent => textContent === `#${hashtag.get('name')}` || textContent === hashtag.get('name'))) + ), [hashtags, renderedHashtags]); + + const [expanded, setExpanded] = useState(false); + const handleClick = useCallback(() => setExpanded(true), []); + + if (invisibleHashtags.isEmpty()) { + return null; + } + + const revealedHashtags = expanded ? invisibleHashtags : invisibleHashtags.take(VISIBLE_HASHTAGS); + + return ( +
+ {revealedHashtags.map(hashtag => ( + + #{hashtag.get('name')} + + ))} + + {!expanded && invisibleHashtags.size > VISIBLE_HASHTAGS && } +
+ ); +}; + +HashtagBar.propTypes = { + hashtags: ImmutablePropTypes.list, + text: PropTypes.string, +}; \ No newline at end of file diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 3aa021ba14..f8edbe163e 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -23,6 +23,7 @@ import { displayMedia } from '../initial_state'; import { Avatar } from './avatar'; import { AvatarOverlay } from './avatar_overlay'; import { DisplayName } from './display_name'; +import { HashtagBar } from './hashtag_bar'; import { RelativeTimestamp } from './relative_timestamp'; import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; @@ -612,6 +613,8 @@ class Status extends ImmutablePureComponent { {media} + + {emojiReactionsBar} diff --git a/app/javascript/mastodon/features/account/components/header.jsx b/app/javascript/mastodon/features/account/components/header.jsx index b64b63d5e7..4646b8d188 100644 --- a/app/javascript/mastodon/features/account/components/header.jsx +++ b/app/javascript/mastodon/features/account/components/header.jsx @@ -267,9 +267,9 @@ class Header extends ImmutablePureComponent { if (signedIn && !account.get('relationship')) { // Wait until the relationship is loaded actionBtn = ''; } else if (account.getIn(['relationship', 'requested'])) { - actionBtn =