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';
 | 
			
		||||
 | 
			
		||||
export const ContentWarning: React.FC<{
 | 
			
		||||
  text: string;
 | 
			
		||||
  expanded?: boolean;
 | 
			
		||||
  onClick?: () => void;
 | 
			
		||||
  icons?: React.ReactNode[];
 | 
			
		||||
  icons?: IconName[];
 | 
			
		||||
}> = ({ text, expanded, onClick, icons }) => (
 | 
			
		||||
  <StatusBanner
 | 
			
		||||
    expanded={expanded}
 | 
			
		||||
    onClick={onClick}
 | 
			
		||||
    variant={BannerVariant.Warning}
 | 
			
		||||
  >
 | 
			
		||||
    {icons}
 | 
			
		||||
    {icons?.map((icon) => (
 | 
			
		||||
      <MediaIcon
 | 
			
		||||
        className='status__content__spoiler-icon'
 | 
			
		||||
        icon={icon}
 | 
			
		||||
        key={`icon-${icon}`}
 | 
			
		||||
      />
 | 
			
		||||
    ))}
 | 
			
		||||
    <p dangerouslySetInnerHTML={{ __html: text }} />
 | 
			
		||||
  </StatusBanner>
 | 
			
		||||
);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 { 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 {
 | 
			
		|||
          <div ref={this.handleRef} className='status focusable' tabIndex={unfocusable ? null : 0}>
 | 
			
		||||
            <span>{status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])}</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>
 | 
			
		||||
        </HotKeys>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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(<PollContainer pollId={status.get('poll')} status={status} lang={language} />);
 | 
			
		||||
      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 (
 | 
			
		||||
      <HotKeys handlers={handlers} tabIndex={unfocusable ? null : -1}>
 | 
			
		||||
| 
						 | 
				
			
			@ -704,26 +691,35 @@ class Status extends ImmutablePureComponent {
 | 
			
		|||
                </Permalink>
 | 
			
		||||
                <StatusIcons
 | 
			
		||||
                  status={status}
 | 
			
		||||
                  mediaIcons={contentMediaIcons.concat(extraMediaIcons)}
 | 
			
		||||
                  mediaIcons={mediaIcons}
 | 
			
		||||
                  settings={settings.get('status_icons')}
 | 
			
		||||
                />
 | 
			
		||||
              </header>
 | 
			
		||||
            )}
 | 
			
		||||
            <StatusContent
 | 
			
		||||
              status={status}
 | 
			
		||||
              onClick={this.handleClick}
 | 
			
		||||
              onTranslate={this.handleTranslate}
 | 
			
		||||
              collapsible
 | 
			
		||||
              media={contentMedia}
 | 
			
		||||
              extraMedia={extraMedia}
 | 
			
		||||
              mediaIcons={contentMediaIcons}
 | 
			
		||||
              expanded={isExpanded}
 | 
			
		||||
              onExpandedToggle={this.handleExpandedToggle}
 | 
			
		||||
              onCollapsedToggle={this.handleCollapsedToggle}
 | 
			
		||||
              tagLinks={settings.get('tag_misleading_links')}
 | 
			
		||||
              rewriteMentions={settings.get('rewrite_mentions')}
 | 
			
		||||
              {...statusContentProps}
 | 
			
		||||
            />
 | 
			
		||||
 | 
			
		||||
            {status.get('spoiler_text').length > 0 && <ContentWarning text={status.getIn(['translation', 'spoilerHtml']) || status.get('spoilerHtml')} expanded={expanded} onClick={this.handleExpandedToggle} icons={mediaIcons} />}
 | 
			
		||||
 | 
			
		||||
            {expanded && (
 | 
			
		||||
              <>
 | 
			
		||||
                <StatusContent
 | 
			
		||||
                  status={status}
 | 
			
		||||
                  onClick={this.handleClick}
 | 
			
		||||
                  onTranslate={this.handleTranslate}
 | 
			
		||||
                  collapsible
 | 
			
		||||
                  media={media}
 | 
			
		||||
                  onCollapsedToggle={this.handleCollapsedToggle}
 | 
			
		||||
                  tagLinks={settings.get('tag_misleading_links')}
 | 
			
		||||
                  rewriteMentions={settings.get('rewrite_mentions')}
 | 
			
		||||
                  {...statusContentProps}
 | 
			
		||||
                />
 | 
			
		||||
 | 
			
		||||
                {media}
 | 
			
		||||
                {hashtagBar}
 | 
			
		||||
              </>
 | 
			
		||||
            )}
 | 
			
		||||
 | 
			
		||||
            {/* This is a glitch-soc addition to have a placeholder */}
 | 
			
		||||
            {!expanded && <MentionsPlaceholder status={status} />}
 | 
			
		||||
 | 
			
		||||
            <StatusActionBar
 | 
			
		||||
              status={status}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,19 +10,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		|||
import { connect } from 'react-redux';
 | 
			
		||||
 | 
			
		||||
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 PollContainer from 'flavours/glitch/containers/poll_container';
 | 
			
		||||
import { identityContextPropShape, withIdentity } from 'flavours/glitch/identity_context';
 | 
			
		||||
import { autoPlayGif, languages as preloadedLanguages } from 'flavours/glitch/initial_state';
 | 
			
		||||
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 textMatchesTarget = (text, origin, host) => {
 | 
			
		||||
| 
						 | 
				
			
			@ -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 {
 | 
			
		|||
      <TranslateButton onClick={this.handleTranslate} translation={status.get('translation')} />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (status.get('spoiler_text').length > 0) {
 | 
			
		||||
      let mentionsPlaceholder = '';
 | 
			
		||||
 | 
			
		||||
      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>;
 | 
			
		||||
      }
 | 
			
		||||
    const poll = !!status.get('poll') && (
 | 
			
		||||
      <PollContainer pollId={status.get('poll')} status={status} lang={language} />
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (this.props.onClick) {
 | 
			
		||||
      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}
 | 
			
		||||
 | 
			
		||||
          <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}
 | 
			
		||||
            {poll}
 | 
			
		||||
            {translateButton}
 | 
			
		||||
          </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}
 | 
			
		||||
          {media}
 | 
			
		||||
          {extraMedia}
 | 
			
		||||
        </div>
 | 
			
		||||
        </>
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      return (
 | 
			
		||||
        <div
 | 
			
		||||
          className='status__content'
 | 
			
		||||
          tabIndex={0}
 | 
			
		||||
        >
 | 
			
		||||
          <div
 | 
			
		||||
            ref={this.setContentsRef}
 | 
			
		||||
            key={`contents-${tagLinks}`}
 | 
			
		||||
            className='status__content__text translate'
 | 
			
		||||
            dangerouslySetInnerHTML={content}
 | 
			
		||||
            tabIndex={0}
 | 
			
		||||
            onMouseEnter={this.handleMouseEnter}
 | 
			
		||||
            onMouseLeave={this.handleMouseLeave}
 | 
			
		||||
            lang={language}
 | 
			
		||||
          />
 | 
			
		||||
        <div className={classNames} ref={this.setRef} tabIndex={0} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
 | 
			
		||||
          <div className='status__content__text status__content__text--visible translate' lang={language} dangerouslySetInnerHTML={content} />
 | 
			
		||||
 | 
			
		||||
          {poll}
 | 
			
		||||
          {translateButton}
 | 
			
		||||
          {media}
 | 
			
		||||
          {extraMedia}
 | 
			
		||||
        </div>
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -8,23 +8,14 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
			
		|||
 | 
			
		||||
import ForumIcon from '@/material-icons/400-24px/forum.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 { MediaIcon } from 'flavours/glitch/components/media_icon';
 | 
			
		||||
import { languages } from 'flavours/glitch/initial_state';
 | 
			
		||||
 | 
			
		||||
import { VisibilityIcon } from './visibility_icon';
 | 
			
		||||
 | 
			
		||||
const messages = defineMessages({
 | 
			
		||||
  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' },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,47 +45,6 @@ class StatusIcons extends PureComponent {
 | 
			
		|||
    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 () {
 | 
			
		||||
    const {
 | 
			
		||||
      status,
 | 
			
		||||
| 
						 | 
				
			
			@ -122,7 +72,7 @@ class StatusIcons extends PureComponent {
 | 
			
		|||
            aria-hidden='true'
 | 
			
		||||
            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')} />}
 | 
			
		||||
      </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' />
 | 
			
		||||
          <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
 | 
			
		||||
          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>
 | 
			
		||||
          <h2><FormattedMessage id='settings.content_warnings_unfold_opts' defaultMessage='Auto-unfolding options' /></h2>
 | 
			
		||||
          <DeprecatedLocalSettingsPageItem
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
                  @typescript-eslint/no-unsafe-assignment */
 | 
			
		||||
 | 
			
		||||
import type { CSSProperties } from 'react';
 | 
			
		||||
import { useState, useRef, useCallback } from 'react';
 | 
			
		||||
import React, { useState, useRef, useCallback } from 'react';
 | 
			
		||||
 | 
			
		||||
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 AttachmentList from 'flavours/glitch/components/attachment_list';
 | 
			
		||||
import { ContentWarning } from 'flavours/glitch/components/content_warning';
 | 
			
		||||
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
 | 
			
		||||
import type { StatusLike } from 'flavours/glitch/components/hashtag_bar';
 | 
			
		||||
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
 | 
			
		||||
import { IconLogo } from 'flavours/glitch/components/logo';
 | 
			
		||||
import { MentionsPlaceholder } from 'flavours/glitch/components/mentions_placeholder';
 | 
			
		||||
import { Permalink } from 'flavours/glitch/components/permalink';
 | 
			
		||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
 | 
			
		||||
import { VisibilityIcon } from 'flavours/glitch/components/visibility_icon';
 | 
			
		||||
import PollContainer from 'flavours/glitch/containers/poll_container';
 | 
			
		||||
import { useAppSelector } from 'flavours/glitch/store';
 | 
			
		||||
 | 
			
		||||
import { Avatar } from '../../../components/avatar';
 | 
			
		||||
| 
						 | 
				
			
			@ -82,13 +83,6 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
    (state) =>
 | 
			
		||||
      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(
 | 
			
		||||
    (state) =>
 | 
			
		||||
      state.local_settings.getIn(['media', 'letterbox'], false) as boolean,
 | 
			
		||||
| 
						 | 
				
			
			@ -108,6 +102,10 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
    [onOpenVideo, status],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleExpandedToggle = useCallback(() => {
 | 
			
		||||
    if (onToggleHidden) onToggleHidden(status);
 | 
			
		||||
  }, [onToggleHidden, status]);
 | 
			
		||||
 | 
			
		||||
  const _measureHeight = useCallback(
 | 
			
		||||
    (heightJustChanged?: boolean) => {
 | 
			
		||||
      if (measureHeight && nodeRef.current) {
 | 
			
		||||
| 
						 | 
				
			
			@ -132,10 +130,6 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
    [_measureHeight],
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const handleChildUpdate = useCallback(() => {
 | 
			
		||||
    _measureHeight();
 | 
			
		||||
  }, [_measureHeight]);
 | 
			
		||||
 | 
			
		||||
  const handleTranslate = useCallback(() => {
 | 
			
		||||
    if (onTranslate) onTranslate(status);
 | 
			
		||||
  }, [onTranslate, status]);
 | 
			
		||||
| 
						 | 
				
			
			@ -144,23 +138,11 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let media;
 | 
			
		||||
  let applicationLink;
 | 
			
		||||
  let reblogLink;
 | 
			
		||||
 | 
			
		||||
  //  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.
 | 
			
		||||
  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 mediaIcons: string[] = [];
 | 
			
		||||
 | 
			
		||||
  const outerStyle = { boxSizing: 'border-box' } as CSSProperties;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -172,7 +154,7 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
    status.getIn(['translation', 'language']) || status.get('language');
 | 
			
		||||
 | 
			
		||||
  if (pictureInPicture.get('inUse')) {
 | 
			
		||||
    media.push(<PictureInPicturePlaceholder />);
 | 
			
		||||
    media = <PictureInPicturePlaceholder />;
 | 
			
		||||
    mediaIcons.push('video-camera');
 | 
			
		||||
  } else if (status.get('media_attachments').size > 0) {
 | 
			
		||||
    if (
 | 
			
		||||
| 
						 | 
				
			
			@ -182,14 +164,14 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
          (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 (
 | 
			
		||||
      ['image', 'gifv', 'unknown'].includes(
 | 
			
		||||
        status.getIn(['media_attachments', 0, 'type']) as string,
 | 
			
		||||
      ) ||
 | 
			
		||||
      status.get('media_attachments').size > 1
 | 
			
		||||
    ) {
 | 
			
		||||
      media.push(
 | 
			
		||||
      media = (
 | 
			
		||||
        <MediaGallery
 | 
			
		||||
          standalone
 | 
			
		||||
          sensitive={status.get('sensitive')}
 | 
			
		||||
| 
						 | 
				
			
			@ -202,7 +184,7 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
          onOpenMedia={onOpenMedia}
 | 
			
		||||
          visible={showMedia}
 | 
			
		||||
          onToggleVisibility={onToggleMediaVisibility}
 | 
			
		||||
        />,
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
      mediaIcons.push('picture-o');
 | 
			
		||||
    } else if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +193,7 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
        attachment.getIn(['translation', 'description']) ||
 | 
			
		||||
        attachment.get('description');
 | 
			
		||||
 | 
			
		||||
      media.push(
 | 
			
		||||
      media = (
 | 
			
		||||
        <Audio
 | 
			
		||||
          src={attachment.get('url')}
 | 
			
		||||
          alt={description}
 | 
			
		||||
| 
						 | 
				
			
			@ -229,7 +211,7 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
          blurhash={attachment.get('blurhash')}
 | 
			
		||||
          height={150}
 | 
			
		||||
          onToggleVisibility={onToggleMediaVisibility}
 | 
			
		||||
        />,
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
      mediaIcons.push('music');
 | 
			
		||||
    } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
 | 
			
		||||
| 
						 | 
				
			
			@ -238,7 +220,7 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
        attachment.getIn(['translation', 'description']) ||
 | 
			
		||||
        attachment.get('description');
 | 
			
		||||
 | 
			
		||||
      media.push(
 | 
			
		||||
      media = (
 | 
			
		||||
        <Video
 | 
			
		||||
          preview={attachment.get('preview_url')}
 | 
			
		||||
          frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
 | 
			
		||||
| 
						 | 
				
			
			@ -257,31 +239,23 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
          letterbox={letterboxMedia}
 | 
			
		||||
          fullwidth={fullwidthMedia}
 | 
			
		||||
          preventPlayback={!expanded}
 | 
			
		||||
        />,
 | 
			
		||||
        />
 | 
			
		||||
      );
 | 
			
		||||
      mediaIcons.push('video-camera');
 | 
			
		||||
    }
 | 
			
		||||
  } else if (status.get('spoiler_text').length === 0) {
 | 
			
		||||
    media.push(
 | 
			
		||||
    media = (
 | 
			
		||||
      <Card
 | 
			
		||||
        sensitive={status.get('sensitive')}
 | 
			
		||||
        onOpenMedia={onOpenMedia}
 | 
			
		||||
        card={status.get('card', null)}
 | 
			
		||||
      />,
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
    mediaIcons.push('link');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (status.get('poll')) {
 | 
			
		||||
    contentMedia.push(
 | 
			
		||||
      <PollContainer
 | 
			
		||||
        pollId={status.get('poll')}
 | 
			
		||||
        // @ts-expect-error -- Poll/PollContainer is not typed yet
 | 
			
		||||
        status={status}
 | 
			
		||||
        lang={status.get('language')}
 | 
			
		||||
      />,
 | 
			
		||||
    );
 | 
			
		||||
    contentMediaIcons.push('tasks');
 | 
			
		||||
    mediaIcons.push('tasks');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (status.get('application')) {
 | 
			
		||||
| 
						 | 
				
			
			@ -345,7 +319,8 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
  const { statusContentProps, hashtagBar } = getHashtagBarForStatus(
 | 
			
		||||
    status as StatusLike,
 | 
			
		||||
  );
 | 
			
		||||
  contentMedia.push(hashtagBar);
 | 
			
		||||
 | 
			
		||||
  expanded ||= status.get('spoiler_text').length === 0;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={outerStyle}>
 | 
			
		||||
| 
						 | 
				
			
			@ -379,20 +354,34 @@ export const DetailedStatus: React.FC<{
 | 
			
		|||
          )}
 | 
			
		||||
        </Permalink>
 | 
			
		||||
 | 
			
		||||
        <StatusContent
 | 
			
		||||
          status={status}
 | 
			
		||||
          media={contentMedia}
 | 
			
		||||
          extraMedia={extraMedia}
 | 
			
		||||
          mediaIcons={contentMediaIcons}
 | 
			
		||||
          expanded={expanded}
 | 
			
		||||
          collapsed={false}
 | 
			
		||||
          onExpandedToggle={onToggleHidden}
 | 
			
		||||
          onTranslate={handleTranslate}
 | 
			
		||||
          onUpdate={handleChildUpdate}
 | 
			
		||||
          tagLinks={tagMisleadingLinks}
 | 
			
		||||
          rewriteMentions={rewriteMentions}
 | 
			
		||||
          {...(statusContentProps as any)}
 | 
			
		||||
        />
 | 
			
		||||
        {status.get('spoiler_text').length > 0 && (
 | 
			
		||||
          <ContentWarning
 | 
			
		||||
            text={
 | 
			
		||||
              status.getIn(['translation', 'spoilerHtml']) ||
 | 
			
		||||
              status.get('spoilerHtml')
 | 
			
		||||
            }
 | 
			
		||||
            expanded={expanded}
 | 
			
		||||
            onClick={handleExpandedToggle}
 | 
			
		||||
          />
 | 
			
		||||
        )}
 | 
			
		||||
 | 
			
		||||
        {expanded && (
 | 
			
		||||
          <>
 | 
			
		||||
            <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__line'>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,8 +68,6 @@
 | 
			
		|||
  "settings.content_warnings": "Content Warnings",
 | 
			
		||||
  "settings.content_warnings.regexp": "Regular expression",
 | 
			
		||||
  "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_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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -23,7 +23,6 @@ const initialState = ImmutableMap({
 | 
			
		|||
  rewrite_mentions: 'no',
 | 
			
		||||
  content_warnings : ImmutableMap({
 | 
			
		||||
    filter       : null,
 | 
			
		||||
    media_outside: false,
 | 
			
		||||
    shared_state : false,
 | 
			
		||||
  }),
 | 
			
		||||
  media     : ImmutableMap({
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1133,11 +1133,6 @@ body > [data-popper-placement] {
 | 
			
		|||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status__content {
 | 
			
		||||
  // glitch: necessary for fullwidth media options
 | 
			
		||||
  overflow: visible;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.reply-indicator {
 | 
			
		||||
  display: grid;
 | 
			
		||||
  grid-template-columns: 46px minmax(0, 1fr);
 | 
			
		||||
| 
						 | 
				
			
			@ -1351,7 +1346,6 @@ body > [data-popper-placement] {
 | 
			
		|||
 | 
			
		||||
.status__content.status__content--collapsed .status__content__text {
 | 
			
		||||
  max-height: 20px * 15; // 15 lines is roughly above 500 characters
 | 
			
		||||
  overflow: hidden;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status__content__read-more-button,
 | 
			
		||||
| 
						 | 
				
			
			@ -1499,11 +1493,25 @@ body > [data-popper-placement] {
 | 
			
		|||
    border-bottom: 0;
 | 
			
		||||
 | 
			
		||||
    .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;
 | 
			
		||||
      width: calc(100% - $thread-margin);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .more-from-author {
 | 
			
		||||
      width: calc(100% - $thread-margin + 2px);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    .status__content__read-more-button {
 | 
			
		||||
      margin-inline-start: $thread-margin;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -1659,14 +1667,6 @@ body > [data-popper-placement] {
 | 
			
		|||
  .media-gallery__item-thumbnail {
 | 
			
		||||
    cursor: default;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  .content-warning {
 | 
			
		||||
    margin-bottom: 16px;
 | 
			
		||||
 | 
			
		||||
    &:last-child {
 | 
			
		||||
      margin-bottom: 0;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.status__prepend {
 | 
			
		||||
| 
						 | 
				
			
			@ -10934,7 +10934,17 @@ noscript {
 | 
			
		|||
  $icon-margin: 48px; // 40px avatar + 8px gap
 | 
			
		||||
 | 
			
		||||
  .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;
 | 
			
		||||
    width: calc(100% - $icon-margin);
 | 
			
		||||
  }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue