diff --git a/app/javascript/flavours/glitch/components/content_warning.tsx b/app/javascript/flavours/glitch/components/content_warning.tsx index 982365b0392308..a261719d2d6a21 100644 --- a/app/javascript/flavours/glitch/components/content_warning.tsx +++ b/app/javascript/flavours/glitch/components/content_warning.tsx @@ -1,17 +1,25 @@ +import type { IconName } from './media_icon'; +import { MediaIcon } from './media_icon'; import { StatusBanner, BannerVariant } from './status_banner'; export const ContentWarning: React.FC<{ text: string; expanded?: boolean; onClick?: () => void; - icons?: React.ReactNode[]; + icons?: IconName[]; }> = ({ text, expanded, onClick, icons }) => ( - {icons} + {icons?.map((icon) => ( + + ))}

); diff --git a/app/javascript/flavours/glitch/components/media_icon.tsx b/app/javascript/flavours/glitch/components/media_icon.tsx new file mode 100644 index 00000000000000..61082fa243bf7c --- /dev/null +++ b/app/javascript/flavours/glitch/components/media_icon.tsx @@ -0,0 +1,55 @@ +import { defineMessages, useIntl } from 'react-intl'; + +import ImageIcon from '@/material-icons/400-24px/image.svg?react'; +import InsertChartIcon from '@/material-icons/400-24px/insert_chart.svg?react'; +import LinkIcon from '@/material-icons/400-24px/link.svg?react'; +import MovieIcon from '@/material-icons/400-24px/movie.svg?react'; +import MusicNoteIcon from '@/material-icons/400-24px/music_note.svg?react'; +import { Icon } from 'flavours/glitch/components/icon'; + +const messages = defineMessages({ + link: { + id: 'status.has_preview_card', + defaultMessage: 'Features an attached preview card', + }, + 'picture-o': { + id: 'status.has_pictures', + defaultMessage: 'Features attached pictures', + }, + tasks: { id: 'status.is_poll', defaultMessage: 'This toot is a poll' }, + 'video-camera': { + id: 'status.has_video', + defaultMessage: 'Features attached videos', + }, + music: { + id: 'status.has_audio', + defaultMessage: 'Features attached audio files', + }, +}); + +const iconComponents = { + link: LinkIcon, + 'picture-o': ImageIcon, + tasks: InsertChartIcon, + 'video-camera': MovieIcon, + music: MusicNoteIcon, +}; + +export type IconName = keyof typeof iconComponents; + +export const MediaIcon: React.FC<{ + className?: string; + icon: IconName; +}> = ({ className, icon }) => { + const intl = useIntl(); + + return ( +

+ {status.get('mentions').map(item => ( + + @{item.get('username')} + + )).reduce((aggregate, item) => [...aggregate, item, ' '], [])} +
+ ); +}; + +MentionsPlaceholder.propTypes = { + status: ImmutablePropTypes.map.isRequired, +}; + \ 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 23da4effb2305e..f45b41e9238e4f 100644 --- a/app/javascript/flavours/glitch/components/status.jsx +++ b/app/javascript/flavours/glitch/components/status.jsx @@ -9,8 +9,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { HotKeys } from 'react-hotkeys'; +import { ContentWarning } from 'flavours/glitch/components/content_warning'; import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder'; -import PollContainer from 'flavours/glitch/containers/poll_container'; import NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container'; import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning'; import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router'; @@ -28,6 +28,7 @@ import { Avatar } from './avatar'; import { AvatarOverlay } from './avatar_overlay'; import { DisplayName } from './display_name'; import { getHashtagBarForStatus } from './hashtag_bar'; +import { MentionsPlaceholder } from './mentions_placeholder'; import { Permalink } from './permalink'; import StatusActionBar from './status_action_bar'; import StatusContent from './status_content'; @@ -134,7 +135,7 @@ class Status extends ImmutablePureComponent { showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault), revealBehindCW: undefined, showCard: false, - forceFilter: undefined, + showDespiteFilter: undefined, }; // Avoid checking props that are functions (and whose equality will always @@ -158,7 +159,7 @@ class Status extends ImmutablePureComponent { updateOnStates = [ 'isExpanded', 'showMedia', - 'forceFilter', + 'showDespiteFilter', ]; static getDerivedStateFromProps(nextProps, prevState) { @@ -242,7 +243,7 @@ class Status extends ImmutablePureComponent { if (this.props.status?.get('id') !== prevProps.status?.get('id')) { this.setState({ showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault), - forceFilter: undefined, + showDespiteFilter: undefined, }); } } @@ -399,12 +400,12 @@ class Status extends ImmutablePureComponent { }; handleUnfilterClick = e => { - this.setState({ forceFilter: false }); + this.setState({ showDespiteFilter: false }); e.preventDefault(); }; handleFilterClick = () => { - this.setState({ forceFilter: true }); + this.setState({ showDespiteFilter: true }); }; handleRef = c => { @@ -448,27 +449,16 @@ class Status extends ImmutablePureComponent { } = this.props; let attachments = null; - // Depending on user settings, some media are considered as parts of the - // contents (affected by CW) while other will be displayed outside of the - // CW. - let contentMedia = []; - let contentMediaIcons = []; - let extraMedia = []; - let extraMediaIcons = []; - let media = contentMedia; - let mediaIcons = contentMediaIcons; + let media = []; + let mediaIcons = []; let statusAvatar; - if (settings.getIn(['content_warnings', 'media_outside'])) { - media = extraMedia; - mediaIcons = extraMediaIcons; - } - if (status === null) { return null; } const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded; + const expanded = isExpanded || status.get('spoiler_text').length === 0; const handlers = { reply: this.handleHotkeyReply, @@ -498,13 +488,13 @@ class Status extends ImmutablePureComponent {
{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} {status.get('spoiler_text').length > 0 && ({status.get('spoiler_text')})} - {isExpanded && {status.get('content')}} + {expanded && {status.get('content')}}
); } - if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) { + if (this.state.showDespiteFilter === undefined ? matchedFilters : this.state.showDespiteFilter) { const minHandlers = this.props.muted ? {} : { moveUp: this.handleHotkeyMoveUp, moveDown: this.handleHotkeyMoveDown, @@ -552,7 +542,7 @@ class Status extends ImmutablePureComponent { sensitive={status.get('sensitive')} letterbox={settings.getIn(['media', 'letterbox'])} fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])} - hidden={!isExpanded} + hidden={!expanded} onOpenMedia={this.handleOpenMedia} cacheWidth={this.props.cacheMediaWidth} defaultWidth={this.props.cachedMediaWidth} @@ -609,7 +599,7 @@ class Status extends ImmutablePureComponent { sensitive={status.get('sensitive')} letterbox={settings.getIn(['media', 'letterbox'])} fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])} - preventPlayback={!isExpanded} + preventPlayback={!expanded} onOpenVideo={this.handleOpenVideo} deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined} visible={this.state.showMedia} @@ -631,9 +621,7 @@ class Status extends ImmutablePureComponent { } if (status.get('poll')) { - const language = status.getIn(['translation', 'language']) || status.get('language'); - contentMedia.push(); - contentMediaIcons.push('tasks'); + mediaIcons.push('tasks'); } // Here we prepare extra data-* attributes for CSS selectors. @@ -672,7 +660,6 @@ class Status extends ImmutablePureComponent { } const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); - contentMedia.push(hashtagBar); return ( @@ -704,26 +691,35 @@ class Status extends ImmutablePureComponent { )} - + + {status.get('spoiler_text').length > 0 && } + + {expanded && ( + <> + + + {media} + {hashtagBar} + + )} + + {/* This is a glitch-soc addition to have a placeholder */} + {!expanded && } { @@ -135,16 +128,10 @@ class StatusContent extends PureComponent { identity: identityContextPropShape, status: ImmutablePropTypes.map.isRequired, statusContent: PropTypes.string, - expanded: PropTypes.bool, - onExpandedToggle: PropTypes.func, onTranslate: PropTypes.func, - media: PropTypes.node, - extraMedia: PropTypes.node, - mediaIcons: PropTypes.arrayOf(PropTypes.string), onClick: PropTypes.func, collapsible: PropTypes.bool, onCollapsedToggle: PropTypes.func, - onUpdate: PropTypes.func, tagLinks: PropTypes.bool, rewriteMentions: PropTypes.string, languages: ImmutablePropTypes.map, @@ -160,12 +147,8 @@ class StatusContent extends PureComponent { rewriteMentions: 'no', }; - state = { - hidden: true, - }; - _updateStatusLinks () { - const node = this.contentsNode; + const node = this.node; const { tagLinks, rewriteMentions } = this.props; if (!node) { @@ -280,7 +263,6 @@ class StatusContent extends PureComponent { componentDidUpdate () { this._updateStatusLinks(); - if (this.props.onUpdate) this.props.onUpdate(); } onMentionClick = (mention, e) => { @@ -326,49 +308,27 @@ class StatusContent extends PureComponent { this.startXY = null; }; - handleSpoilerClick = (e) => { - e.preventDefault(); - - if (this.props.onExpandedToggle) { - this.props.onExpandedToggle(); - } else { - this.setState({ hidden: !this.state.hidden }); - } - }; - handleTranslate = () => { this.props.onTranslate(); }; - setContentsRef = (c) => { - this.contentsNode = c; + setRef = (c) => { + this.node = c; }; render () { - const { - status, - media, - extraMedia, - mediaIcons, - tagLinks, - rewriteMentions, - intl, - statusContent, - } = this.props; + const { status, intl, statusContent } = this.props; const renderReadMore = this.props.onClick && status.get('collapsed'); - const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const contentLocale = intl.locale.replace(/[_-].*/, ''); const targetLanguages = this.props.languages?.get(status.get('language') || 'und'); const renderTranslate = this.props.onTranslate && this.props.identity.signedIn && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('search_index').trim().length > 0 && targetLanguages?.includes(contentLocale); const content = { __html: statusContent ?? getStatusContent(status) }; - const spoilerHtml = status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml'); const language = status.getIn(['translation', 'language']) || status.get('language'); const classNames = classnames('status__content', { 'status__content--with-action': this.props.onClick && this.props.history, 'status__content--collapsed': renderReadMore, - 'status__content--with-spoiler': status.get('spoiler_text').length > 0, }); const readMoreButton = renderReadMore && ( @@ -381,113 +341,30 @@ class StatusContent extends PureComponent { ); - if (status.get('spoiler_text').length > 0) { - let mentionsPlaceholder = ''; - - const mentionLinks = status.get('mentions').map(item => ( - - @{item.get('username')} - - )).reduce((aggregate, item) => [...aggregate, item, ' '], []); - - let spoilerIcons = []; - if (mediaIcons) { - const mediaComponents = { - 'link': LinkIcon, - 'picture-o': ImageIcon, - 'tasks': InsertChartIcon, - 'video-camera': MovieIcon, - 'music': MusicNoteIcon, - }; - - spoilerIcons = mediaIcons.map((mediaIcon) => ( -