Change status content markup to match upstream (#2923)
* Remove option to have media outside of CWs Upstream adopted the media-in-CW design glitch-soc originally had. * Move poll to StatusContent * Refactor status media icons * Rename `forceFilter` to `showDespiteFilter` for consistency with upstream * Change media and status content markup to match upstream's * Add mention placeholders back
This commit is contained in:
parent
5e65586161
commit
b7afca0f05
@ -1,17 +1,25 @@
|
|||||||
|
import type { IconName } from './media_icon';
|
||||||
|
import { MediaIcon } from './media_icon';
|
||||||
import { StatusBanner, BannerVariant } from './status_banner';
|
import { StatusBanner, BannerVariant } from './status_banner';
|
||||||
|
|
||||||
export const ContentWarning: React.FC<{
|
export const ContentWarning: React.FC<{
|
||||||
text: string;
|
text: string;
|
||||||
expanded?: boolean;
|
expanded?: boolean;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
icons?: React.ReactNode[];
|
icons?: IconName[];
|
||||||
}> = ({ text, expanded, onClick, icons }) => (
|
}> = ({ text, expanded, onClick, icons }) => (
|
||||||
<StatusBanner
|
<StatusBanner
|
||||||
expanded={expanded}
|
expanded={expanded}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
variant={BannerVariant.Warning}
|
variant={BannerVariant.Warning}
|
||||||
>
|
>
|
||||||
{icons}
|
{icons?.map((icon) => (
|
||||||
|
<MediaIcon
|
||||||
|
className='status__content__spoiler-icon'
|
||||||
|
icon={icon}
|
||||||
|
key={`icon-${icon}`}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
<p dangerouslySetInnerHTML={{ __html: text }} />
|
<p dangerouslySetInnerHTML={{ __html: text }} />
|
||||||
</StatusBanner>
|
</StatusBanner>
|
||||||
);
|
);
|
||||||
|
55
app/javascript/flavours/glitch/components/media_icon.tsx
Normal file
55
app/javascript/flavours/glitch/components/media_icon.tsx
Normal file
@ -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 (
|
||||||
|
<Icon
|
||||||
|
className={className}
|
||||||
|
id={icon}
|
||||||
|
icon={iconComponents[icon]}
|
||||||
|
title={intl.formatMessage(messages[icon])}
|
||||||
|
aria-hidden='true'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,29 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
|
||||||
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
|
|
||||||
|
export const MentionsPlaceholder = ({ status }) => {
|
||||||
|
if (status.get('spoiler_text').length === 0 || !status.get('mentions')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='status__content'>
|
||||||
|
{status.get('mentions').map(item => (
|
||||||
|
<Permalink
|
||||||
|
to={`/@${item.get('acct')}`}
|
||||||
|
href={item.get('url')}
|
||||||
|
key={item.get('id')}
|
||||||
|
className='mention'
|
||||||
|
>
|
||||||
|
@<span>{item.get('username')}</span>
|
||||||
|
</Permalink>
|
||||||
|
)).reduce((aggregate, item) => [...aggregate, item, ' '], [])}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
MentionsPlaceholder.propTypes = {
|
||||||
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
|
};
|
||||||
|
|
@ -9,8 +9,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||||||
|
|
||||||
import { HotKeys } from 'react-hotkeys';
|
import { HotKeys } from 'react-hotkeys';
|
||||||
|
|
||||||
|
import { ContentWarning } from 'flavours/glitch/components/content_warning';
|
||||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
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 NotificationOverlayContainer from 'flavours/glitch/features/notifications/containers/overlay_container';
|
||||||
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
|
import { autoUnfoldCW } from 'flavours/glitch/utils/content_warning';
|
||||||
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
import { withOptionalRouter, WithOptionalRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||||
@ -28,6 +28,7 @@ import { Avatar } from './avatar';
|
|||||||
import { AvatarOverlay } from './avatar_overlay';
|
import { AvatarOverlay } from './avatar_overlay';
|
||||||
import { DisplayName } from './display_name';
|
import { DisplayName } from './display_name';
|
||||||
import { getHashtagBarForStatus } from './hashtag_bar';
|
import { getHashtagBarForStatus } from './hashtag_bar';
|
||||||
|
import { MentionsPlaceholder } from './mentions_placeholder';
|
||||||
import { Permalink } from './permalink';
|
import { Permalink } from './permalink';
|
||||||
import StatusActionBar from './status_action_bar';
|
import StatusActionBar from './status_action_bar';
|
||||||
import StatusContent from './status_content';
|
import StatusContent from './status_content';
|
||||||
@ -134,7 +135,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
||||||
revealBehindCW: undefined,
|
revealBehindCW: undefined,
|
||||||
showCard: false,
|
showCard: false,
|
||||||
forceFilter: undefined,
|
showDespiteFilter: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Avoid checking props that are functions (and whose equality will always
|
// Avoid checking props that are functions (and whose equality will always
|
||||||
@ -158,7 +159,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
updateOnStates = [
|
updateOnStates = [
|
||||||
'isExpanded',
|
'isExpanded',
|
||||||
'showMedia',
|
'showMedia',
|
||||||
'forceFilter',
|
'showDespiteFilter',
|
||||||
];
|
];
|
||||||
|
|
||||||
static getDerivedStateFromProps(nextProps, prevState) {
|
static getDerivedStateFromProps(nextProps, prevState) {
|
||||||
@ -242,7 +243,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
if (this.props.status?.get('id') !== prevProps.status?.get('id')) {
|
if (this.props.status?.get('id') !== prevProps.status?.get('id')) {
|
||||||
this.setState({
|
this.setState({
|
||||||
showMedia: defaultMediaVisibility(this.props.status, this.props.settings) && !(this.context?.hideMediaByDefault),
|
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 => {
|
handleUnfilterClick = e => {
|
||||||
this.setState({ forceFilter: false });
|
this.setState({ showDespiteFilter: false });
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleFilterClick = () => {
|
handleFilterClick = () => {
|
||||||
this.setState({ forceFilter: true });
|
this.setState({ showDespiteFilter: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
handleRef = c => {
|
handleRef = c => {
|
||||||
@ -448,27 +449,16 @@ class Status extends ImmutablePureComponent {
|
|||||||
} = this.props;
|
} = this.props;
|
||||||
let attachments = null;
|
let attachments = null;
|
||||||
|
|
||||||
// Depending on user settings, some media are considered as parts of the
|
let media = [];
|
||||||
// contents (affected by CW) while other will be displayed outside of the
|
let mediaIcons = [];
|
||||||
// CW.
|
|
||||||
let contentMedia = [];
|
|
||||||
let contentMediaIcons = [];
|
|
||||||
let extraMedia = [];
|
|
||||||
let extraMediaIcons = [];
|
|
||||||
let media = contentMedia;
|
|
||||||
let mediaIcons = contentMediaIcons;
|
|
||||||
let statusAvatar;
|
let statusAvatar;
|
||||||
|
|
||||||
if (settings.getIn(['content_warnings', 'media_outside'])) {
|
|
||||||
media = extraMedia;
|
|
||||||
mediaIcons = extraMediaIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status === null) {
|
if (status === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isExpanded = settings.getIn(['content_warnings', 'shared_state']) ? !status.get('hidden') : this.state.isExpanded;
|
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 = {
|
const handlers = {
|
||||||
reply: this.handleHotkeyReply,
|
reply: this.handleHotkeyReply,
|
||||||
@ -498,13 +488,13 @@ class Status extends ImmutablePureComponent {
|
|||||||
<div ref={this.handleRef} className='status focusable' tabIndex={unfocusable ? null : 0}>
|
<div ref={this.handleRef} className='status focusable' tabIndex={unfocusable ? null : 0}>
|
||||||
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
<span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</span>
|
||||||
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
{status.get('spoiler_text').length > 0 && (<span>{status.get('spoiler_text')}</span>)}
|
||||||
{isExpanded && <span>{status.get('content')}</span>}
|
{expanded && <span>{status.get('content')}</span>}
|
||||||
</div>
|
</div>
|
||||||
</HotKeys>
|
</HotKeys>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
if (this.state.showDespiteFilter === undefined ? matchedFilters : this.state.showDespiteFilter) {
|
||||||
const minHandlers = this.props.muted ? {} : {
|
const minHandlers = this.props.muted ? {} : {
|
||||||
moveUp: this.handleHotkeyMoveUp,
|
moveUp: this.handleHotkeyMoveUp,
|
||||||
moveDown: this.handleHotkeyMoveDown,
|
moveDown: this.handleHotkeyMoveDown,
|
||||||
@ -552,7 +542,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
hidden={!isExpanded}
|
hidden={!expanded}
|
||||||
onOpenMedia={this.handleOpenMedia}
|
onOpenMedia={this.handleOpenMedia}
|
||||||
cacheWidth={this.props.cacheMediaWidth}
|
cacheWidth={this.props.cacheMediaWidth}
|
||||||
defaultWidth={this.props.cachedMediaWidth}
|
defaultWidth={this.props.cachedMediaWidth}
|
||||||
@ -609,7 +599,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
letterbox={settings.getIn(['media', 'letterbox'])}
|
letterbox={settings.getIn(['media', 'letterbox'])}
|
||||||
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
fullwidth={!rootId && settings.getIn(['media', 'fullwidth'])}
|
||||||
preventPlayback={!isExpanded}
|
preventPlayback={!expanded}
|
||||||
onOpenVideo={this.handleOpenVideo}
|
onOpenVideo={this.handleOpenVideo}
|
||||||
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
|
||||||
visible={this.state.showMedia}
|
visible={this.state.showMedia}
|
||||||
@ -631,9 +621,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
mediaIcons.push('tasks');
|
||||||
contentMedia.push(<PollContainer pollId={status.get('poll')} status={status} lang={language} />);
|
|
||||||
contentMediaIcons.push('tasks');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Here we prepare extra data-* attributes for CSS selectors.
|
// Here we prepare extra data-* attributes for CSS selectors.
|
||||||
@ -672,7 +660,6 @@ class Status extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);
|
||||||
contentMedia.push(hashtagBar);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
<HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
|
||||||
@ -704,26 +691,35 @@ class Status extends ImmutablePureComponent {
|
|||||||
</Permalink>
|
</Permalink>
|
||||||
<StatusIcons
|
<StatusIcons
|
||||||
status={status}
|
status={status}
|
||||||
mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
|
mediaIcons={mediaIcons}
|
||||||
settings={settings.get('status_icons')}
|
settings={settings.get('status_icons')}
|
||||||
/>
|
/>
|
||||||
</header>
|
</header>
|
||||||
)}
|
)}
|
||||||
<StatusContent
|
|
||||||
status={status}
|
{status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} icons={mediaIcons} />}
|
||||||
onClick={this.handleClick}
|
|
||||||
onTranslate={this.handleTranslate}
|
{expanded && (
|
||||||
collapsible
|
<>
|
||||||
media={contentMedia}
|
<StatusContent
|
||||||
extraMedia={extraMedia}
|
status={status}
|
||||||
mediaIcons={contentMediaIcons}
|
onClick={this.handleClick}
|
||||||
expanded={isExpanded}
|
onTranslate={this.handleTranslate}
|
||||||
onExpandedToggle={this.handleExpandedToggle}
|
collapsible
|
||||||
onCollapsedToggle={this.handleCollapsedToggle}
|
media={media}
|
||||||
tagLinks={settings.get('tag_misleading_links')}
|
onCollapsedToggle={this.handleCollapsedToggle}
|
||||||
rewriteMentions={settings.get('rewrite_mentions')}
|
tagLinks={settings.get('tag_misleading_links')}
|
||||||
{...statusContentProps}
|
rewriteMentions={settings.get('rewrite_mentions')}
|
||||||
/>
|
{...statusContentProps}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{media}
|
||||||
|
{hashtagBar}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* This is a glitch-soc addition to have a placeholder */}
|
||||||
|
{!expanded && <MentionsPlaceholder status={status} />}
|
||||||
|
|
||||||
<StatusActionBar
|
<StatusActionBar
|
||||||
status={status}
|
status={status}
|
||||||
|
@ -10,19 +10,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
import ChevronRightIcon from '@/material-icons/400-24px/chevron_right.svg?react';
|
||||||
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 { ContentWarning } from 'flavours/glitch/components/content_warning';
|
|
||||||
import { Icon } from 'flavours/glitch/components/icon';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import PollContainer from 'flavours/glitch/containers/poll_container';
|
||||||
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
|
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
|
||||||
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
|
||||||
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
||||||
|
|
||||||
import { Permalink } from './permalink';
|
|
||||||
|
|
||||||
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||||
|
|
||||||
const textMatchesTarget = (text, origin, host) => {
|
const textMatchesTarget = (text, origin, host) => {
|
||||||
@ -135,16 +128,10 @@ class StatusContent extends PureComponent {
|
|||||||
identity: identityContextPropShape,
|
identity: identityContextPropShape,
|
||||||
status: ImmutablePropTypes.map.isRequired,
|
status: ImmutablePropTypes.map.isRequired,
|
||||||
statusContent: PropTypes.string,
|
statusContent: PropTypes.string,
|
||||||
expanded: PropTypes.bool,
|
|
||||||
onExpandedToggle: PropTypes.func,
|
|
||||||
onTranslate: PropTypes.func,
|
onTranslate: PropTypes.func,
|
||||||
media: PropTypes.node,
|
|
||||||
extraMedia: PropTypes.node,
|
|
||||||
mediaIcons: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
onClick: PropTypes.func,
|
onClick: PropTypes.func,
|
||||||
collapsible: PropTypes.bool,
|
collapsible: PropTypes.bool,
|
||||||
onCollapsedToggle: PropTypes.func,
|
onCollapsedToggle: PropTypes.func,
|
||||||
onUpdate: PropTypes.func,
|
|
||||||
tagLinks: PropTypes.bool,
|
tagLinks: PropTypes.bool,
|
||||||
rewriteMentions: PropTypes.string,
|
rewriteMentions: PropTypes.string,
|
||||||
languages: ImmutablePropTypes.map,
|
languages: ImmutablePropTypes.map,
|
||||||
@ -160,12 +147,8 @@ class StatusContent extends PureComponent {
|
|||||||
rewriteMentions: 'no',
|
rewriteMentions: 'no',
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
|
||||||
hidden: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
_updateStatusLinks () {
|
_updateStatusLinks () {
|
||||||
const node = this.contentsNode;
|
const node = this.node;
|
||||||
const { tagLinks, rewriteMentions } = this.props;
|
const { tagLinks, rewriteMentions } = this.props;
|
||||||
|
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@ -280,7 +263,6 @@ class StatusContent extends PureComponent {
|
|||||||
|
|
||||||
componentDidUpdate () {
|
componentDidUpdate () {
|
||||||
this._updateStatusLinks();
|
this._updateStatusLinks();
|
||||||
if (this.props.onUpdate) this.props.onUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMentionClick = (mention, e) => {
|
onMentionClick = (mention, e) => {
|
||||||
@ -326,49 +308,27 @@ class StatusContent extends PureComponent {
|
|||||||
this.startXY = null;
|
this.startXY = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSpoilerClick = (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (this.props.onExpandedToggle) {
|
|
||||||
this.props.onExpandedToggle();
|
|
||||||
} else {
|
|
||||||
this.setState({ hidden: !this.state.hidden });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTranslate = () => {
|
handleTranslate = () => {
|
||||||
this.props.onTranslate();
|
this.props.onTranslate();
|
||||||
};
|
};
|
||||||
|
|
||||||
setContentsRef = (c) => {
|
setRef = (c) => {
|
||||||
this.contentsNode = c;
|
this.node = c;
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const { status, intl, statusContent } = this.props;
|
||||||
status,
|
|
||||||
media,
|
|
||||||
extraMedia,
|
|
||||||
mediaIcons,
|
|
||||||
tagLinks,
|
|
||||||
rewriteMentions,
|
|
||||||
intl,
|
|
||||||
statusContent,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
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 contentLocale = intl.locale.replace(/[_-].*/, '');
|
||||||
const targetLanguages = this.props.languages?.get(status.get('language') || 'und');
|
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 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 content = { __html: statusContent ?? getStatusContent(status) };
|
||||||
const spoilerHtml = status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml');
|
|
||||||
const language = status.getIn(['translation', 'language']) || status.get('language');
|
const language = status.getIn(['translation', 'language']) || status.get('language');
|
||||||
const classNames = classnames('status__content', {
|
const classNames = classnames('status__content', {
|
||||||
'status__content--with-action': this.props.onClick && this.props.history,
|
'status__content--with-action': this.props.onClick && this.props.history,
|
||||||
'status__content--collapsed': renderReadMore,
|
'status__content--collapsed': renderReadMore,
|
||||||
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const readMoreButton = renderReadMore && (
|
const readMoreButton = renderReadMore && (
|
||||||
@ -381,113 +341,30 @@ class StatusContent extends PureComponent {
|
|||||||
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
<TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status.get('spoiler_text').length > 0) {
|
const poll = !!status.get('poll') && (
|
||||||
let mentionsPlaceholder = '';
|
<PollContainer pollId={status.get('poll')} status={status} lang={language} />
|
||||||
|
);
|
||||||
const mentionLinks = status.get('mentions').map(item => (
|
|
||||||
<Permalink
|
|
||||||
to={`/@${item.get('acct')}`}
|
|
||||||
href={item.get('url')}
|
|
||||||
key={item.get('id')}
|
|
||||||
className='mention'
|
|
||||||
>
|
|
||||||
@<span>{item.get('username')}</span>
|
|
||||||
</Permalink>
|
|
||||||
)).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) => (
|
|
||||||
<Icon
|
|
||||||
fixedWidth
|
|
||||||
className='status__content__spoiler-icon'
|
|
||||||
id={mediaIcon}
|
|
||||||
icon={mediaComponents[mediaIcon]}
|
|
||||||
aria-hidden='true'
|
|
||||||
key={`icon-${mediaIcon}`}
|
|
||||||
/>
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hidden) {
|
|
||||||
mentionsPlaceholder = <div>{mentionLinks}</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (this.props.onClick) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames} tabIndex={0} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
|
<>
|
||||||
<ContentWarning text={spoilerHtml} expanded={!hidden} onClick={this.handleSpoilerClick} icons={spoilerIcons} />
|
<div className={classNames} ref={this.setRef} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} tabIndex={0} key='status-content' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
|
<div className='status__content__text status__content__text--visible translate' lang={language} dangerouslySetInnerHTML={content} />
|
||||||
|
|
||||||
{mentionsPlaceholder}
|
{poll}
|
||||||
|
{translateButton}
|
||||||
<div className={`status__content__spoiler ${!hidden ? 'status__content__spoiler--visible' : ''}`}>
|
|
||||||
<div
|
|
||||||
ref={this.setContentsRef}
|
|
||||||
key={`contents-${tagLinks}`}
|
|
||||||
tabIndex={!hidden ? 0 : null}
|
|
||||||
dangerouslySetInnerHTML={content}
|
|
||||||
className='status__content__text translate'
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
lang={language}
|
|
||||||
/>
|
|
||||||
{!hidden && translateButton}
|
|
||||||
{media}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{extraMedia}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
} else if (this.props.onClick) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={classNames}
|
|
||||||
onMouseDown={this.handleMouseDown}
|
|
||||||
onMouseUp={this.handleMouseUp}
|
|
||||||
tabIndex={0}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
ref={this.setContentsRef}
|
|
||||||
key={`contents-${tagLinks}-${rewriteMentions}`}
|
|
||||||
dangerouslySetInnerHTML={content}
|
|
||||||
className='status__content__text translate'
|
|
||||||
tabIndex={0}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
lang={language}
|
|
||||||
/>
|
|
||||||
{translateButton}
|
|
||||||
{readMoreButton}
|
{readMoreButton}
|
||||||
{media}
|
</>
|
||||||
{extraMedia}
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={classNames} ref={this.setRef} tabIndex={0} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||||
className='status__content'
|
<div className='status__content__text status__content__text--visible translate' lang={language} dangerouslySetInnerHTML={content} />
|
||||||
tabIndex={0}
|
|
||||||
>
|
{poll}
|
||||||
<div
|
|
||||||
ref={this.setContentsRef}
|
|
||||||
key={`contents-${tagLinks}`}
|
|
||||||
className='status__content__text translate'
|
|
||||||
dangerouslySetInnerHTML={content}
|
|
||||||
tabIndex={0}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
lang={language}
|
|
||||||
/>
|
|
||||||
{translateButton}
|
{translateButton}
|
||||||
{media}
|
|
||||||
{extraMedia}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
|
|
||||||
import ForumIcon from '@/material-icons/400-24px/forum.svg?react';
|
import ForumIcon from '@/material-icons/400-24px/forum.svg?react';
|
||||||
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
import HomeIcon from '@/material-icons/400-24px/home.svg?react';
|
||||||
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';
|
import { Icon } from 'flavours/glitch/components/icon';
|
||||||
|
import { MediaIcon } from 'flavours/glitch/components/media_icon';
|
||||||
import { languages } from 'flavours/glitch/initial_state';
|
import { languages } from 'flavours/glitch/initial_state';
|
||||||
|
|
||||||
import { VisibilityIcon } from './visibility_icon';
|
import { VisibilityIcon } from './visibility_icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
inReplyTo: { id: 'status.in_reply_to', defaultMessage: 'This toot is a reply' },
|
inReplyTo: { id: 'status.in_reply_to', defaultMessage: 'This toot is a reply' },
|
||||||
previewCard: { id: 'status.has_preview_card', defaultMessage: 'Features an attached preview card' },
|
|
||||||
pictures: { id: 'status.has_pictures', defaultMessage: 'Features attached pictures' },
|
|
||||||
poll: { id: 'status.is_poll', defaultMessage: 'This toot is a poll' },
|
|
||||||
video: { id: 'status.has_video', defaultMessage: 'Features attached videos' },
|
|
||||||
audio: { id: 'status.has_audio', defaultMessage: 'Features attached audio files' },
|
|
||||||
localOnly: { id: 'status.local_only', defaultMessage: 'Only visible from your instance' },
|
localOnly: { id: 'status.local_only', defaultMessage: 'Only visible from your instance' },
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -54,47 +45,6 @@ class StatusIcons extends PureComponent {
|
|||||||
settings: ImmutablePropTypes.map.isRequired,
|
settings: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
renderIcon (mediaIcon) {
|
|
||||||
const { intl } = this.props;
|
|
||||||
|
|
||||||
let title, iconComponent;
|
|
||||||
|
|
||||||
switch (mediaIcon) {
|
|
||||||
case 'link':
|
|
||||||
title = messages.previewCard;
|
|
||||||
iconComponent = LinkIcon;
|
|
||||||
break;
|
|
||||||
case 'picture-o':
|
|
||||||
title = messages.pictures;
|
|
||||||
iconComponent = ImageIcon;
|
|
||||||
break;
|
|
||||||
case 'tasks':
|
|
||||||
title = messages.poll;
|
|
||||||
iconComponent = InsertChartIcon;
|
|
||||||
break;
|
|
||||||
case 'video-camera':
|
|
||||||
title = messages.video;
|
|
||||||
iconComponent = MovieIcon;
|
|
||||||
break;
|
|
||||||
case 'music':
|
|
||||||
title = messages.audio;
|
|
||||||
iconComponent = MusicNoteIcon;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Icon
|
|
||||||
fixedWidth
|
|
||||||
className='status__media-icon'
|
|
||||||
key={`media-icon--${mediaIcon}`}
|
|
||||||
id={mediaIcon}
|
|
||||||
icon={iconComponent}
|
|
||||||
aria-hidden='true'
|
|
||||||
title={title && intl.formatMessage(title)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
status,
|
status,
|
||||||
@ -122,7 +72,7 @@ class StatusIcons extends PureComponent {
|
|||||||
aria-hidden='true'
|
aria-hidden='true'
|
||||||
title={intl.formatMessage(messages.localOnly)}
|
title={intl.formatMessage(messages.localOnly)}
|
||||||
/>}
|
/>}
|
||||||
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => this.renderIcon(icon))}
|
{settings.get('media') && !!mediaIcons && mediaIcons.map(icon => (<MediaIcon key={`media-icon--${icon}`} className='status__media-icon' icon={icon} />))}
|
||||||
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
|
{settings.get('visibility') && <VisibilityIcon visibility={status.get('visibility')} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -274,15 +274,6 @@ class LocalSettingsPage extends PureComponent {
|
|||||||
<FormattedMessage id='settings.content_warnings_shared_state' defaultMessage='Show/hide content of all copies at once' />
|
<FormattedMessage id='settings.content_warnings_shared_state' defaultMessage='Show/hide content of all copies at once' />
|
||||||
<span className='hint'><FormattedMessage id='settings.content_warnings_shared_state_hint' defaultMessage='Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW' /></span>
|
<span className='hint'><FormattedMessage id='settings.content_warnings_shared_state_hint' defaultMessage='Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW' /></span>
|
||||||
</LocalSettingsPageItem>
|
</LocalSettingsPageItem>
|
||||||
<LocalSettingsPageItem
|
|
||||||
settings={settings}
|
|
||||||
item={['content_warnings', 'media_outside']}
|
|
||||||
id='mastodon-settings--content_warnings-media_outside'
|
|
||||||
onChange={onChange}
|
|
||||||
>
|
|
||||||
<FormattedMessage id='settings.content_warnings_media_outside' defaultMessage='Display media attachments outside content warnings' />
|
|
||||||
<span className='hint'><FormattedMessage id='settings.content_warnings_media_outside_hint' defaultMessage='Reproduce upstream Mastodon behavior by having the Content Warning toggle not affect media attachments' /></span>
|
|
||||||
</LocalSettingsPageItem>
|
|
||||||
<section>
|
<section>
|
||||||
<h2><FormattedMessage id='settings.content_warnings_unfold_opts' defaultMessage='Auto-unfolding options' /></h2>
|
<h2><FormattedMessage id='settings.content_warnings_unfold_opts' defaultMessage='Auto-unfolding options' /></h2>
|
||||||
<DeprecatedLocalSettingsPageItem
|
<DeprecatedLocalSettingsPageItem
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
@typescript-eslint/no-unsafe-assignment */
|
@typescript-eslint/no-unsafe-assignment */
|
||||||
|
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { useState, useRef, useCallback } from 'react';
|
import React, { useState, useRef, useCallback } from 'react';
|
||||||
|
|
||||||
import { FormattedDate, FormattedMessage } from 'react-intl';
|
import { FormattedDate, FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
@ -13,14 +13,15 @@ import { Link } from 'react-router-dom';
|
|||||||
|
|
||||||
import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
|
import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
|
||||||
import AttachmentList from 'flavours/glitch/components/attachment_list';
|
import AttachmentList from 'flavours/glitch/components/attachment_list';
|
||||||
|
import { ContentWarning } from 'flavours/glitch/components/content_warning';
|
||||||
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
|
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
|
||||||
import type { StatusLike } from 'flavours/glitch/components/hashtag_bar';
|
import type { StatusLike } from 'flavours/glitch/components/hashtag_bar';
|
||||||
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
|
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
|
||||||
import { IconLogo } from 'flavours/glitch/components/logo';
|
import { IconLogo } from 'flavours/glitch/components/logo';
|
||||||
|
import { MentionsPlaceholder } from 'flavours/glitch/components/mentions_placeholder';
|
||||||
import { Permalink } from 'flavours/glitch/components/permalink';
|
import { Permalink } from 'flavours/glitch/components/permalink';
|
||||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
||||||
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
|
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
|
||||||
import PollContainer from 'flavours/glitch/containers/poll_container';
|
|
||||||
import { useAppSelector } from 'flavours/glitch/store';
|
import { useAppSelector } from 'flavours/glitch/store';
|
||||||
|
|
||||||
import { Avatar } from '../../../components/avatar';
|
import { Avatar } from '../../../components/avatar';
|
||||||
@ -82,13 +83,6 @@ export const DetailedStatus: React.FC<{
|
|||||||
(state) =>
|
(state) =>
|
||||||
state.local_settings.get('tag_misleading_links', false) as boolean,
|
state.local_settings.get('tag_misleading_links', false) as boolean,
|
||||||
);
|
);
|
||||||
const mediaOutsideCW = useAppSelector(
|
|
||||||
(state) =>
|
|
||||||
state.local_settings.getIn(
|
|
||||||
['content_warnings', 'media_outside'],
|
|
||||||
false,
|
|
||||||
) as boolean,
|
|
||||||
);
|
|
||||||
const letterboxMedia = useAppSelector(
|
const letterboxMedia = useAppSelector(
|
||||||
(state) =>
|
(state) =>
|
||||||
state.local_settings.getIn(['media', 'letterbox'], false) as boolean,
|
state.local_settings.getIn(['media', 'letterbox'], false) as boolean,
|
||||||
@ -108,6 +102,10 @@ export const DetailedStatus: React.FC<{
|
|||||||
[onOpenVideo, status],
|
[onOpenVideo, status],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleExpandedToggle = useCallback(() => {
|
||||||
|
if (onToggleHidden) onToggleHidden(status);
|
||||||
|
}, [onToggleHidden, status]);
|
||||||
|
|
||||||
const _measureHeight = useCallback(
|
const _measureHeight = useCallback(
|
||||||
(heightJustChanged?: boolean) => {
|
(heightJustChanged?: boolean) => {
|
||||||
if (measureHeight && nodeRef.current) {
|
if (measureHeight && nodeRef.current) {
|
||||||
@ -132,10 +130,6 @@ export const DetailedStatus: React.FC<{
|
|||||||
[_measureHeight],
|
[_measureHeight],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleChildUpdate = useCallback(() => {
|
|
||||||
_measureHeight();
|
|
||||||
}, [_measureHeight]);
|
|
||||||
|
|
||||||
const handleTranslate = useCallback(() => {
|
const handleTranslate = useCallback(() => {
|
||||||
if (onTranslate) onTranslate(status);
|
if (onTranslate) onTranslate(status);
|
||||||
}, [onTranslate, status]);
|
}, [onTranslate, status]);
|
||||||
@ -144,23 +138,11 @@ export const DetailedStatus: React.FC<{
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let media;
|
||||||
let applicationLink;
|
let applicationLink;
|
||||||
let reblogLink;
|
let reblogLink;
|
||||||
|
|
||||||
// Depending on user settings, some media are considered as parts of the
|
const mediaIcons: string[] = [];
|
||||||
// contents (affected by CW) while other will be displayed outside of the
|
|
||||||
// CW.
|
|
||||||
const contentMedia: React.ReactNode[] = [];
|
|
||||||
const contentMediaIcons: string[] = [];
|
|
||||||
const extraMedia: React.ReactNode[] = [];
|
|
||||||
const extraMediaIcons: string[] = [];
|
|
||||||
let media = contentMedia;
|
|
||||||
let mediaIcons: string[] = contentMediaIcons;
|
|
||||||
|
|
||||||
if (mediaOutsideCW) {
|
|
||||||
media = extraMedia;
|
|
||||||
mediaIcons = extraMediaIcons;
|
|
||||||
}
|
|
||||||
|
|
||||||
const outerStyle = { boxSizing: 'border-box' } as CSSProperties;
|
const outerStyle = { boxSizing: 'border-box' } as CSSProperties;
|
||||||
|
|
||||||
@ -172,7 +154,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
status.getIn(['translation', 'language']) || status.get('language');
|
status.getIn(['translation', 'language']) || status.get('language');
|
||||||
|
|
||||||
if (pictureInPicture.get('inUse')) {
|
if (pictureInPicture.get('inUse')) {
|
||||||
media.push(<PictureInPicturePlaceholder />);
|
media = <PictureInPicturePlaceholder />;
|
||||||
mediaIcons.push('video-camera');
|
mediaIcons.push('video-camera');
|
||||||
} else if (status.get('media_attachments').size > 0) {
|
} else if (status.get('media_attachments').size > 0) {
|
||||||
if (
|
if (
|
||||||
@ -182,14 +164,14 @@ export const DetailedStatus: React.FC<{
|
|||||||
(item: Immutable.Map<string, any>) => item.get('type') === 'unknown',
|
(item: Immutable.Map<string, any>) => item.get('type') === 'unknown',
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
media.push(<AttachmentList media={status.get('media_attachments')} />);
|
media = <AttachmentList media={status.get('media_attachments')} />;
|
||||||
} else if (
|
} else if (
|
||||||
['image', 'gifv', 'unknown'].includes(
|
['image', 'gifv', 'unknown'].includes(
|
||||||
status.getIn(['media_attachments', 0, 'type']) as string,
|
status.getIn(['media_attachments', 0, 'type']) as string,
|
||||||
) ||
|
) ||
|
||||||
status.get('media_attachments').size > 1
|
status.get('media_attachments').size > 1
|
||||||
) {
|
) {
|
||||||
media.push(
|
media = (
|
||||||
<MediaGallery
|
<MediaGallery
|
||||||
standalone
|
standalone
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
@ -202,7 +184,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
onOpenMedia={onOpenMedia}
|
onOpenMedia={onOpenMedia}
|
||||||
visible={showMedia}
|
visible={showMedia}
|
||||||
onToggleVisibility={onToggleMediaVisibility}
|
onToggleVisibility={onToggleMediaVisibility}
|
||||||
/>,
|
/>
|
||||||
);
|
);
|
||||||
mediaIcons.push('picture-o');
|
mediaIcons.push('picture-o');
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
} else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
|
||||||
@ -211,7 +193,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
attachment.getIn(['translation', 'description']) ||
|
attachment.getIn(['translation', 'description']) ||
|
||||||
attachment.get('description');
|
attachment.get('description');
|
||||||
|
|
||||||
media.push(
|
media = (
|
||||||
<Audio
|
<Audio
|
||||||
src={attachment.get('url')}
|
src={attachment.get('url')}
|
||||||
alt={description}
|
alt={description}
|
||||||
@ -229,7 +211,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
blurhash={attachment.get('blurhash')}
|
blurhash={attachment.get('blurhash')}
|
||||||
height={150}
|
height={150}
|
||||||
onToggleVisibility={onToggleMediaVisibility}
|
onToggleVisibility={onToggleMediaVisibility}
|
||||||
/>,
|
/>
|
||||||
);
|
);
|
||||||
mediaIcons.push('music');
|
mediaIcons.push('music');
|
||||||
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
} else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||||
@ -238,7 +220,7 @@ export const DetailedStatus: React.FC<{
|
|||||||
attachment.getIn(['translation', 'description']) ||
|
attachment.getIn(['translation', 'description']) ||
|
||||||
attachment.get('description');
|
attachment.get('description');
|
||||||
|
|
||||||
media.push(
|
media = (
|
||||||
<Video
|
<Video
|
||||||
preview={attachment.get('preview_url')}
|
preview={attachment.get('preview_url')}
|
||||||
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
|
||||||
@ -257,31 +239,23 @@ export const DetailedStatus: React.FC<{
|
|||||||
letterbox={letterboxMedia}
|
letterbox={letterboxMedia}
|
||||||
fullwidth={fullwidthMedia}
|
fullwidth={fullwidthMedia}
|
||||||
preventPlayback={!expanded}
|
preventPlayback={!expanded}
|
||||||
/>,
|
/>
|
||||||
);
|
);
|
||||||
mediaIcons.push('video-camera');
|
mediaIcons.push('video-camera');
|
||||||
}
|
}
|
||||||
} else if (status.get('spoiler_text').length === 0) {
|
} else if (status.get('spoiler_text').length === 0) {
|
||||||
media.push(
|
media = (
|
||||||
<Card
|
<Card
|
||||||
sensitive={status.get('sensitive')}
|
sensitive={status.get('sensitive')}
|
||||||
onOpenMedia={onOpenMedia}
|
onOpenMedia={onOpenMedia}
|
||||||
card={status.get('card', null)}
|
card={status.get('card', null)}
|
||||||
/>,
|
/>
|
||||||
);
|
);
|
||||||
mediaIcons.push('link');
|
mediaIcons.push('link');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('poll')) {
|
if (status.get('poll')) {
|
||||||
contentMedia.push(
|
mediaIcons.push('tasks');
|
||||||
<PollContainer
|
|
||||||
pollId={status.get('poll')}
|
|
||||||
// @ts-expect-error -- Poll/PollContainer is not typed yet
|
|
||||||
status={status}
|
|
||||||
lang={status.get('language')}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
contentMediaIcons.push('tasks');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.get('application')) {
|
if (status.get('application')) {
|
||||||
@ -345,7 +319,8 @@ export const DetailedStatus: React.FC<{
|
|||||||
const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
|
const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
|
||||||
status as StatusLike,
|
status as StatusLike,
|
||||||
);
|
);
|
||||||
contentMedia.push(hashtagBar);
|
|
||||||
|
expanded ||= status.get('spoiler_text').length === 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={outerStyle}>
|
<div style={outerStyle}>
|
||||||
@ -379,20 +354,34 @@ export const DetailedStatus: React.FC<{
|
|||||||
)}
|
)}
|
||||||
</Permalink>
|
</Permalink>
|
||||||
|
|
||||||
<StatusContent
|
{status.get('spoiler_text').length > 0 && (
|
||||||
status={status}
|
<ContentWarning
|
||||||
media={contentMedia}
|
text={
|
||||||
extraMedia={extraMedia}
|
status.getIn(['translation', 'spoilerHtml']) ||
|
||||||
mediaIcons={contentMediaIcons}
|
status.get('spoilerHtml')
|
||||||
expanded={expanded}
|
}
|
||||||
collapsed={false}
|
expanded={expanded}
|
||||||
onExpandedToggle={onToggleHidden}
|
onClick={handleExpandedToggle}
|
||||||
onTranslate={handleTranslate}
|
/>
|
||||||
onUpdate={handleChildUpdate}
|
)}
|
||||||
tagLinks={tagMisleadingLinks}
|
|
||||||
rewriteMentions={rewriteMentions}
|
{expanded && (
|
||||||
{...(statusContentProps as any)}
|
<>
|
||||||
/>
|
<StatusContent
|
||||||
|
status={status}
|
||||||
|
onTranslate={handleTranslate}
|
||||||
|
tagLinks={tagMisleadingLinks}
|
||||||
|
rewriteMentions={rewriteMentions}
|
||||||
|
{...(statusContentProps as any)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{media}
|
||||||
|
{hashtagBar}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* This is a glitch-soc addition to have a placeholder */}
|
||||||
|
{!expanded && <MentionsPlaceholder status={status} />}
|
||||||
|
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<div className='detailed-status__meta__line'>
|
<div className='detailed-status__meta__line'>
|
||||||
|
@ -68,8 +68,6 @@
|
|||||||
"settings.content_warnings": "Content Warnings",
|
"settings.content_warnings": "Content Warnings",
|
||||||
"settings.content_warnings.regexp": "Regular expression",
|
"settings.content_warnings.regexp": "Regular expression",
|
||||||
"settings.content_warnings_filter": "Content warnings to not automatically unfold:",
|
"settings.content_warnings_filter": "Content warnings to not automatically unfold:",
|
||||||
"settings.content_warnings_media_outside": "Display media attachments outside content warnings",
|
|
||||||
"settings.content_warnings_media_outside_hint": "Reproduce upstream Mastodon behavior by having the Content Warning toggle not affect media attachments",
|
|
||||||
"settings.content_warnings_shared_state": "Show/hide content of all copies at once",
|
"settings.content_warnings_shared_state": "Show/hide content of all copies at once",
|
||||||
"settings.content_warnings_shared_state_hint": "Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW",
|
"settings.content_warnings_shared_state_hint": "Reproduce upstream Mastodon behavior by having the Content Warning button affect all copies of a post at once. This will prevent automatic collapsing of any copy of a toot with unfolded CW",
|
||||||
"settings.content_warnings_unfold_opts": "Auto-unfolding options",
|
"settings.content_warnings_unfold_opts": "Auto-unfolding options",
|
||||||
|
@ -23,7 +23,6 @@ const initialState = ImmutableMap({
|
|||||||
rewrite_mentions: 'no',
|
rewrite_mentions: 'no',
|
||||||
content_warnings : ImmutableMap({
|
content_warnings : ImmutableMap({
|
||||||
filter : null,
|
filter : null,
|
||||||
media_outside: false,
|
|
||||||
shared_state : false,
|
shared_state : false,
|
||||||
}),
|
}),
|
||||||
media : ImmutableMap({
|
media : ImmutableMap({
|
||||||
|
@ -1133,11 +1133,6 @@ body > [data-popper-placement] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content {
|
|
||||||
// glitch: necessary for fullwidth media options
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-indicator {
|
.reply-indicator {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 46px minmax(0, 1fr);
|
grid-template-columns: 46px minmax(0, 1fr);
|
||||||
@ -1351,7 +1346,6 @@ body > [data-popper-placement] {
|
|||||||
|
|
||||||
.status__content.status__content--collapsed .status__content__text {
|
.status__content.status__content--collapsed .status__content__text {
|
||||||
max-height: 20px * 15; // 15 lines is roughly above 500 characters
|
max-height: 20px * 15; // 15 lines is roughly above 500 characters
|
||||||
overflow: hidden;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__content__read-more-button,
|
.status__content__read-more-button,
|
||||||
@ -1499,11 +1493,25 @@ body > [data-popper-placement] {
|
|||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
|
|
||||||
.status__content,
|
.status__content,
|
||||||
.status__action-bar {
|
.status__action-bar,
|
||||||
|
.media-gallery,
|
||||||
|
.video-player,
|
||||||
|
.audio-player,
|
||||||
|
.attachment-list,
|
||||||
|
.picture-in-picture-placeholder,
|
||||||
|
.more-from-author,
|
||||||
|
.status-card,
|
||||||
|
.hashtag-bar,
|
||||||
|
.content-warning,
|
||||||
|
.filter-warning {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: $thread-margin;
|
||||||
width: calc(100% - $thread-margin);
|
width: calc(100% - $thread-margin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.more-from-author {
|
||||||
|
width: calc(100% - $thread-margin + 2px);
|
||||||
|
}
|
||||||
|
|
||||||
.status__content__read-more-button {
|
.status__content__read-more-button {
|
||||||
margin-inline-start: $thread-margin;
|
margin-inline-start: $thread-margin;
|
||||||
}
|
}
|
||||||
@ -1659,14 +1667,6 @@ body > [data-popper-placement] {
|
|||||||
.media-gallery__item-thumbnail {
|
.media-gallery__item-thumbnail {
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-warning {
|
|
||||||
margin-bottom: 16px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.status__prepend {
|
.status__prepend {
|
||||||
@ -10934,7 +10934,17 @@ noscript {
|
|||||||
$icon-margin: 48px; // 40px avatar + 8px gap
|
$icon-margin: 48px; // 40px avatar + 8px gap
|
||||||
|
|
||||||
.status__content,
|
.status__content,
|
||||||
.status__action-bar {
|
.status__action-bar,
|
||||||
|
.media-gallery,
|
||||||
|
.video-player,
|
||||||
|
.audio-player,
|
||||||
|
.attachment-list,
|
||||||
|
.picture-in-picture-placeholder,
|
||||||
|
.more-from-author,
|
||||||
|
.status-card,
|
||||||
|
.hashtag-bar,
|
||||||
|
.content-warning,
|
||||||
|
.filter-warning {
|
||||||
margin-inline-start: $icon-margin;
|
margin-inline-start: $icon-margin;
|
||||||
width: calc(100% - $icon-margin);
|
width: calc(100% - $icon-margin);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user