From a81ed84453091148f4436c62eb8b2c859406769d Mon Sep 17 00:00:00 2001 From: Claire Date: Mon, 14 Aug 2023 23:42:30 +0200 Subject: [PATCH] [Glitch] Add display of out-of-band hashtags in the web interface Port df6e7198984b42cf8d64e66548742e14264236a0 to glitch-soc Co-authored-by: Eugen Rochko Signed-off-by: Claire --- .../glitch/components/hashtag_bar.jsx | 50 +++++++++++++++++++ .../flavours/glitch/components/status.jsx | 5 ++ .../status/components/detailed_status.jsx | 3 ++ .../glitch/styles/components/misc.scss | 23 +++++++++ 4 files changed, 81 insertions(+) create mode 100644 app/javascript/flavours/glitch/components/hashtag_bar.jsx diff --git a/app/javascript/flavours/glitch/components/hashtag_bar.jsx b/app/javascript/flavours/glitch/components/hashtag_bar.jsx new file mode 100644 index 0000000000..6a39005e16 --- /dev/null +++ b/app/javascript/flavours/glitch/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/flavours/glitch/components/status.jsx b/app/javascript/flavours/glitch/components/status.jsx index 72ea42d90a..c43e913c66 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -20,6 +20,7 @@ import Bundle from '../features/ui/components/bundle'; import { MediaGallery, Video, Audio } from '../features/ui/util/async-components'; import { displayMedia } from '../initial_state'; +import { HashtagBar } from './hashtag_bar'; import AttachmentList from './attachment_list'; import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; @@ -739,6 +740,10 @@ class Status extends ImmutablePureComponent { contentMediaIcons.push('tasks'); } + media.push( + + ); + // Here we prepare extra data-* attributes for CSS selectors. // Users can use those for theming, hiding avatars etc via UserStyle const selectorAttribs = { diff --git a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx index ec8db1febb..f8134cf5ee 100644 --- a/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx +++ b/app/javascript/flavours/glitch/features/status/components/detailed_status.jsx @@ -11,6 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { AnimatedNumber } from 'flavours/glitch/components/animated_number'; import AttachmentList from 'flavours/glitch/components/attachment_list'; import EditedTimestamp from 'flavours/glitch/components/edited_timestamp'; +import { HashtagBar } from 'flavours/glitch/components/hashtag_bar'; import { Icon } from 'flavours/glitch/components/icon'; import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder'; import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon'; @@ -327,6 +328,8 @@ class DetailedStatus extends ImmutablePureComponent { disabled /> + +