Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
naaaaaaaaaaaf committed Oct 2, 2023
1 parent a8b8b57 commit 6ed113f
Show file tree
Hide file tree
Showing 15 changed files with 495 additions and 31 deletions.
1 change: 1 addition & 0 deletions app/javascript/mastodon/components/media_gallery.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class MediaGallery extends PureComponent {
visible: PropTypes.bool,
autoplay: PropTypes.bool,
onToggleVisibility: PropTypes.func,
quote: PropTypes.bool,
};

state = {
Expand Down
5 changes: 4 additions & 1 deletion app/javascript/mastodon/components/poll.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class Poll extends ImmutablePureComponent {

static getDerivedStateFromProps (props, state) {
const { poll } = props;
if (!poll) {
return null;
}
const expires_at = poll.get('expires_at');
const expired = poll.get('expired') || expires_at !== null && (new Date(expires_at)).getTime() < Date.now();
return (expired === state.expired) ? null : { expired };
Expand All @@ -78,7 +81,7 @@ class Poll extends ImmutablePureComponent {
_setupTimer () {
const { poll } = this.props;
clearTimeout(this._timer);
if (!this.state.expired) {
if (!this.state.expired && !!poll) {
const delay = (new Date(poll.get('expires_at'))).getTime() - Date.now();
this._timer = setTimeout(() => {
this.setState({ expired: true });
Expand Down
223 changes: 209 additions & 14 deletions app/javascript/mastodon/components/status.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import PropTypes from 'prop-types';

import { connect } from 'react-redux';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';

import classNames from 'classnames';
Expand Down Expand Up @@ -29,6 +29,30 @@ import StatusContent from './status_content';

const domParser = new DOMParser();

const mapStateToProps = (state, props) => {
let status = props.status;

if (status === null) {
return null;
}

if (status.get('reblog', null) !== null && typeof status.get('reblog') === 'object') {
status = status.get('reblog');
}

if (status.get('quote', null) === null) {
return {
quote_muted: status.get('quote_id', null) ? true : false,
};
}
const id = status.getIn(['quote', 'account', 'id'], null);

return {
quote_muted: id !== null && (state.getIn(['relationships', id, 'muting']) || state.getIn(['relationships', id, 'blocking']) || state.getIn(['relationships', id, 'blocked_by']) || state.getIn(['relationships', id, 'domain_blocking'])) || status.getIn(['quote', 'quote_muted']),
};
};


export const textForScreenReader = (intl, status, rebloggedByText = false) => {
const displayName = status.getIn(['account', 'display_name']);

Expand Down Expand Up @@ -69,7 +93,7 @@ const messages = defineMessages({
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
});

@connect(mapStateToProps)
class Status extends ImmutablePureComponent {

static contextTypes = {
Expand All @@ -79,6 +103,7 @@ class Status extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
account: ImmutablePropTypes.map,
quote_muted: PropTypes.bool,
previousId: PropTypes.string,
nextInReplyToId: PropTypes.string,
rootId: PropTypes.string,
Expand Down Expand Up @@ -116,6 +141,7 @@ class Status extends ImmutablePureComponent {
inUse: PropTypes.bool,
available: PropTypes.bool,
}),
contextType: PropTypes.string,
};

// Avoid checking props that are functions (and whose equality will always
Expand All @@ -131,6 +157,7 @@ class Status extends ImmutablePureComponent {

state = {
showMedia: defaultMediaVisibility(this.props.status),
showQuoteMedia: defaultMediaVisibility(this.props.status ? this.props.status.get('quote', null) : null),
statusId: undefined,
forceFilter: undefined,
};
Expand All @@ -139,6 +166,7 @@ class Status extends ImmutablePureComponent {
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
return {
showMedia: defaultMediaVisibility(nextProps.status),
showQuoteMedia: defaultMediaVisibility(nextProps.status.get('quote', null)),
statusId: nextProps.status.get('id'),
};
} else {
Expand All @@ -150,7 +178,15 @@ class Status extends ImmutablePureComponent {
this.setState({ showMedia: !this.state.showMedia });
};

handleClick = e => {
handleToggleQuoteMediaVisibility = () => {
this.setState({ showQuoteMedia: !this.state.showQuoteMedia });
}

handleQuoteClick = e => {
this.handleClick(e, true);
}

handleClick = (e, quote = false) => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
Expand All @@ -159,14 +195,18 @@ class Status extends ImmutablePureComponent {
e.preventDefault();
}

this.handleHotkeyOpen();
this.handleHotkeyOpen(quote);
};

handlePrependAccountClick = e => {
this.handleAccountClick(e, false);
};

handleAccountClick = (e, proper = true) => {
handleQuoteAccountClick = e => {
this.handleAccountClick(e, true, true);
}

handleAccountClick = (e, proper = true, quote = false) => {
if (e && (e.button !== 0 || e.ctrlKey || e.metaKey)) {
return;
}
Expand All @@ -176,7 +216,7 @@ class Status extends ImmutablePureComponent {
e.stopPropagation();
}

this._openProfile(proper);
this._openProfile(proper, qoute);
};

handleExpandedToggle = () => {
Expand All @@ -191,6 +231,10 @@ class Status extends ImmutablePureComponent {
this.props.onTranslate(this._properStatus());
};

handleExpandedQuoteToggle = () => {
this.props.onToggleHidden(this._properQuoteStatus());
};

getAttachmentAspectRatio () {
const attachments = this._properStatus().get('media_attachments');

Expand Down Expand Up @@ -227,12 +271,21 @@ class Status extends ImmutablePureComponent {
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, options);
};

handleOpenVideoQuote = (options) => {
const status = this._properQuoteStatus();
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
}

handleOpenMedia = (media, index) => {
const status = this._properStatus();
const lang = status.getIn(['translation', 'language']) || status.get('language');
this.props.onOpenMedia(status.get('id'), media, index, lang);
};

handleOpenMediaQuote = (media, index) => {
this.props.onOpenMedia(this._properQuoteStatus().get('id'), media, index);
}

handleHotkeyOpenMedia = e => {
const { onOpenMedia, onOpenVideo } = this.props;
const status = this._properStatus();
Expand Down Expand Up @@ -274,14 +327,14 @@ class Status extends ImmutablePureComponent {
this.props.onMention(this._properStatus().get('account'), this.context.router.history);
};

handleHotkeyOpen = () => {
handleHotkeyOpen = (quote = false) => {
if (this.props.onClick) {
this.props.onClick();
return;
}

const { router } = this.context;
const status = this._properStatus();
const status = quote ? this._properQuoteStatus() : this._properStatus();

if (!router) {
return;
Expand All @@ -294,9 +347,10 @@ class Status extends ImmutablePureComponent {
this._openProfile();
};

_openProfile = (proper = true) => {
_openProfile = (proper = true, quote = false) => {
const { router } = this.context;
const status = proper ? this._properStatus() : this.props.status;
const properStatus = proper ? this._properStatus() : this.props.status;
const status = quote ? properStatus.get('quote') : properStatus;

if (!router) {
return;
Expand Down Expand Up @@ -340,12 +394,22 @@ class Status extends ImmutablePureComponent {
}
}

_properQuoteStatus () {
const status = this._properStatus();

if (status.get('quote', null) !== null && typeof status.get('quote') === 'object') {
return status.get('quote');
} else {
return status;
}
}

handleRef = c => {
this.node = c;
};

render () {
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId } = this.props;
const { intl, hidden, featured, unread, showThread, scrollKey, pictureInPicture, previousId, nextInReplyToId, rootId , contextType, quote_muted} = this.props;

let { status, account, ...other } = this.props;

Expand Down Expand Up @@ -443,9 +507,10 @@ class Status extends ImmutablePureComponent {
);
}

if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder aspectRatio={this.getAttachmentAspectRatio()} />;
} else if (status.get('media_attachments').size > 0) {
if (status.get('media_attachments').size > 0) {
if (pictureInPicture.get('inUse')) {
media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
} else if (this.props.muted) {
const language = status.getIn(['translation', 'language']) || status.get('language');

if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
Expand Down Expand Up @@ -545,6 +610,135 @@ class Status extends ImmutablePureComponent {

const visibilityIcon = visibilityIconInfo[status.get('visibility')];

let quote = null;
if (status.get('quote', null) !== null && typeof status.get('quote') === 'object') {
let quote_status = status.get('quote');

let quote_media = null;
if (quote_status.get('media_attachments').size > 0) {
if (pictureInPicture.get('inUse')) {
quote_media = <PictureInPicturePlaceholder width={this.props.cachedMediaWidth} />;
} else if (this.props.muted) {
quote_media = (
<AttachmentList
compact
media={quote_status.get('media_attachments')}
/>
);
} else if (quote_status.getIn(['media_attachments', 0, 'type']) === 'audio') {
const attachment = quote_status.getIn(['media_attachments', 0]);

quote_media = (
<Bundle fetchComponent={Audio} loading={this.renderLoadingAudioPlayer} >
{Component => (
<Component
src={attachment.get('url')}
alt={attachment.get('description')}
poster={attachment.get('preview_url') || quote_status.getIn(['account', 'avatar_static'])}
backgroundColor={attachment.getIn(['meta', 'colors', 'background'])}
foregroundColor={attachment.getIn(['meta', 'colors', 'foreground'])}
accentColor={attachment.getIn(['meta', 'colors', 'accent'])}
duration={attachment.getIn(['meta', 'original', 'duration'], 0)}
width={this.props.cachedMediaWidth}
height={70}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
quote
/>
)}
</Bundle>
);
} else if (quote_status.getIn(['media_attachments', 0, 'type']) === 'video') {
const attachment = quote_status.getIn(['media_attachments', 0]);

quote_media = (
<Bundle fetchComponent={Video} loading={this.renderLoadingVideoPlayer} >
{Component => (
<Component
preview={attachment.get('preview_url')}
frameRate={attachment.getIn(['meta', 'original', 'frame_rate'])}
blurhash={attachment.get('blurhash')}
src={attachment.get('url')}
alt={attachment.get('description')}
width={this.props.cachedMediaWidth}
height={110}
inline
sensitive={quote_status.get('sensitive')}
onOpenVideo={this.handleOpenVideoQuote}
cacheWidth={this.props.cacheMediaWidth}
deployPictureInPicture={pictureInPicture.get('available') ? this.handleDeployPictureInPicture : undefined}
visible={this.state.showQuoteMedia}
onToggleVisibility={this.handleToggleQuoteMediaVisibility}
quote
/>
)}
</Bundle>
);
} else {
quote_media = (
<Bundle fetchComponent={MediaGallery} loading={this.renderLoadingMediaGallery}>
{Component => (
<Component
media={quote_status.get('media_attachments')}
sensitive={quote_status.get('sensitive')}
height={110}
onOpenMedia={this.handleOpenMediaQuote}
cacheWidth={this.props.cacheMediaWidth}
defaultWidth={this.props.cachedMediaWidth}
visible={this.state.showQuoteMedia}
onToggleVisibility={this.handleToggleQuoteMediaVisibility}
quote
/>
)}
</Bundle>
);
}
}

if (quote_muted) {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { 'status-reply': !!quote_status.get('in_reply_to_id'), muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className={classNames('status__content muted-quote', { 'status__content--with-action': this.context.router })}>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
} else if (quote_status.get('visibility') === 'unlisted' && !!contextType && ['public', 'community', 'hashtag'].includes(contextType.split(':', 2)[0])) {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { 'status-reply': !!quote_status.get('in_reply_to_id'), muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className={classNames('status__content unlisted-quote', { 'status__content--with-action': this.context.router })}>
<button onClick={this.handleQuoteClick}>
<FormattedMessage id='status.unlisted_quote' defaultMessage='Unlisted quote' />
</button>
</div>
</div>
);
} else {
quote = (
<div className={classNames('quote-status', `status-${quote_status.get('visibility')}`, { 'status-reply': !!quote_status.get('in_reply_to_id'), muted: this.props.muted })} data-id={quote_status.get('id')}>
<div className='status__info'>
<a onClick={this.handleQuoteAccountClick} target='_blank' href={quote_status.getIn(['account', 'url'])} title={quote_status.getIn(['account', 'acct'])} className='status__display-name'>
<div className='status__avatar'><Avatar account={quote_status.get('account')} size={18} /></div>
<DisplayName account={quote_status.get('account')} />
</a>
</div>

<StatusContent status={quote_status} onClick={this.handleQuoteClick} expanded={!quote_status.get('hidden')} onExpandedToggle={this.handleExpandedQuoteToggle} quote />

{quote_media}
</div>
);
}
} else if (quote_muted) {
quote = (
<div className={classNames('quote-status', { muted: this.props.muted })}>
<div className={classNames('status__content muted-quote', { 'status__content--with-action': this.context.router })}>
<FormattedMessage id='status.muted_quote' defaultMessage='Muted quote' />
</div>
</div>
);
}

const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status);

return (
Expand Down Expand Up @@ -583,6 +777,7 @@ class Status extends ImmutablePureComponent {
/>

{media}
{quote}

{hashtagBar}

Expand Down

0 comments on commit 6ed113f

Please sign in to comment.