Merge branch 'master' into glitch-soc/merge-upstream
This commit is contained in:
@ -10,7 +10,7 @@ import StatusContent from './status_content';
|
||||
import StatusActionBar from './status_action_bar';
|
||||
import AttachmentList from './attachment_list';
|
||||
import Card from '../features/status/components/card';
|
||||
import { injectIntl, FormattedMessage } from 'react-intl';
|
||||
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { MediaGallery, Video, Audio } from '../features/ui/util/async-components';
|
||||
import { HotKeys } from 'react-hotkeys';
|
||||
@ -51,6 +51,13 @@ export const defaultMediaVisibility = (status) => {
|
||||
return (displayMedia !== 'hide_all' && !status.get('sensitive') || displayMedia === 'show_all');
|
||||
};
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class Status extends ImmutablePureComponent {
|
||||
|
||||
@ -416,6 +423,15 @@ class Status extends ImmutablePureComponent {
|
||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||
}
|
||||
|
||||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
|
||||
return (
|
||||
<HotKeys handlers={handlers}>
|
||||
<div className={classNames('status__wrapper', `status__wrapper-${status.get('visibility')}`, { 'status__wrapper-reply': !!status.get('in_reply_to_id'), read: unread === false, focusable: !this.props.muted })} tabIndex={this.props.muted ? null : 0} data-featured={featured ? 'true' : null} aria-label={textForScreenReader(intl, status, rebloggedByText)} ref={this.handleRef}>
|
||||
@ -425,6 +441,7 @@ class Status extends ImmutablePureComponent {
|
||||
<div className='status__expand' onClick={this.handleExpandClick} role='presentation' />
|
||||
<div className='status__info'>
|
||||
<a href={status.get('url')} className='status__relative-time' target='_blank' rel='noopener noreferrer'><RelativeTimestamp timestamp={status.get('created_at')} /></a>
|
||||
<span className='status__visibility-icon'><Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></span>
|
||||
|
||||
<a onClick={this.handleAccountClick} data-id={status.getIn(['account', 'id'])} href={status.getIn(['account', 'url'])} title={status.getIn(['account', 'acct'])} className='status__display-name' target='_blank' rel='noopener noreferrer'>
|
||||
<div className='status__avatar'>
|
||||
|
@ -237,9 +237,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
const account = status.get('account');
|
||||
|
||||
let menu = [];
|
||||
let reblogIcon = 'retweet';
|
||||
let replyIcon;
|
||||
let replyTitle;
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen });
|
||||
|
||||
@ -259,10 +256,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
if (status.getIn(['account', 'id']) === me) {
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
} else {
|
||||
if (status.get('visibility') === 'private') {
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
|
||||
}
|
||||
}
|
||||
|
||||
menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick });
|
||||
@ -305,12 +298,8 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
if (status.get('visibility') === 'direct') {
|
||||
reblogIcon = 'envelope';
|
||||
} else if (status.get('visibility') === 'private') {
|
||||
reblogIcon = 'lock';
|
||||
}
|
||||
|
||||
let replyIcon;
|
||||
let replyTitle;
|
||||
if (status.get('in_reply_to_id', null) === null) {
|
||||
replyIcon = 'reply';
|
||||
replyTitle = intl.formatMessage(messages.reply);
|
||||
@ -319,6 +308,19 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
replyTitle = intl.formatMessage(messages.replyAll);
|
||||
}
|
||||
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle = '';
|
||||
if (status.get('reblogged')) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
} else if (reblogPrivate) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||
} else {
|
||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
}
|
||||
|
||||
const shareButton = ('share' in navigator) && publicStatus && (
|
||||
<IconButton className='status__action-bar-button' title={intl.formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
|
||||
);
|
||||
@ -326,7 +328,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
return (
|
||||
<div className='status__action-bar'>
|
||||
<div className='status__action-bar__counter'><IconButton className='status__action-bar-button' title={replyTitle} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /><span className='status__action-bar__counter__label' >{obfuscatedCount(status.get('replies_count'))}</span></div>
|
||||
<IconButton className='status__action-bar-button' disabled={!publicStatus} active={status.get('reblogged')} pressed={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button' disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} />
|
||||
<IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} />
|
||||
{shareButton}
|
||||
|
||||
|
@ -154,6 +154,7 @@ class Audio extends React.PureComponent {
|
||||
width: PropTypes.number,
|
||||
height: PropTypes.number,
|
||||
editable: PropTypes.bool,
|
||||
fullscreen: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
cacheWidth: PropTypes.func,
|
||||
};
|
||||
@ -180,7 +181,7 @@ class Audio extends React.PureComponent {
|
||||
|
||||
_setDimensions () {
|
||||
const width = this.player.offsetWidth;
|
||||
const height = width / (16/9);
|
||||
const height = this.props.fullscreen ? this.player.offsetHeight : (width / (16/9));
|
||||
|
||||
if (this.props.cacheWidth) {
|
||||
this.props.cacheWidth(width);
|
||||
@ -291,8 +292,10 @@ class Audio extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleProgress = () => {
|
||||
if (this.audio.buffered.length > 0) {
|
||||
this.setState({ buffer: this.audio.buffered.end(0) / this.audio.duration * 100 });
|
||||
const lastTimeRange = this.audio.buffered.length - 1;
|
||||
|
||||
if (lastTimeRange > -1) {
|
||||
this.setState({ buffer: Math.ceil(this.audio.buffered.end(lastTimeRange) / this.audio.duration * 100) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -349,18 +352,18 @@ class Audio extends React.PureComponent {
|
||||
|
||||
handleMouseMove = throttle(e => {
|
||||
const { x } = getPointerPosition(this.seek, e);
|
||||
const currentTime = Math.floor(this.audio.duration * x);
|
||||
const currentTime = this.audio.duration * x;
|
||||
|
||||
if (!isNaN(currentTime)) {
|
||||
this.setState({ currentTime }, () => {
|
||||
this.audio.currentTime = currentTime;
|
||||
});
|
||||
}
|
||||
}, 60);
|
||||
}, 15);
|
||||
|
||||
handleTimeUpdate = () => {
|
||||
this.setState({
|
||||
currentTime: Math.floor(this.audio.currentTime),
|
||||
currentTime: this.audio.currentTime,
|
||||
duration: Math.floor(this.audio.duration),
|
||||
});
|
||||
}
|
||||
@ -373,7 +376,7 @@ class Audio extends React.PureComponent {
|
||||
this.audio.volume = x;
|
||||
});
|
||||
}
|
||||
}, 60);
|
||||
}, 15);
|
||||
|
||||
handleScroll = throttle(() => {
|
||||
if (!this.canvas || !this.audio) {
|
||||
@ -451,6 +454,7 @@ class Audio extends React.PureComponent {
|
||||
|
||||
_renderCanvas () {
|
||||
requestAnimationFrame(() => {
|
||||
this.handleTimeUpdate();
|
||||
this._clear();
|
||||
this._draw();
|
||||
|
||||
@ -622,7 +626,7 @@ class Audio extends React.PureComponent {
|
||||
const progress = (currentTime / duration) * 100;
|
||||
|
||||
return (
|
||||
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.state.height || this.props.height }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<div className={classNames('audio-player', { editable, 'with-light-background': darkText })} ref={this.setPlayerRef} style={{ width: '100%', height: this.props.fullscreen ? '100%' : (this.state.height || this.props.height) }} onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}>
|
||||
<audio
|
||||
src={src}
|
||||
ref={this.setAudioRef}
|
||||
@ -630,7 +634,6 @@ class Audio extends React.PureComponent {
|
||||
onPlay={this.handlePlay}
|
||||
onPause={this.handlePause}
|
||||
onProgress={this.handleProgress}
|
||||
onTimeUpdate={this.handleTimeUpdate}
|
||||
crossOrigin='anonymous'
|
||||
/>
|
||||
|
||||
@ -691,7 +694,7 @@ class Audio extends React.PureComponent {
|
||||
</div>
|
||||
|
||||
<span className='video-player__time'>
|
||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||
<span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
|
||||
<span className='video-player__time-sep'>/</span>
|
||||
<span className='video-player__time-total'>{formatTime(this.state.duration || Math.floor(this.props.duration))}</span>
|
||||
</span>
|
||||
|
@ -7,11 +7,9 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
const messages = defineMessages({
|
||||
upload: { id: 'upload_button.label', defaultMessage: 'Add media ({formats})' },
|
||||
upload: { id: 'upload_button.label', defaultMessage: 'Add images, a video or an audio file' },
|
||||
});
|
||||
|
||||
const SUPPORTED_FORMATS = 'JPEG, PNG, GIF, WebM, MP4, MOV, OGG, WAV, MP3, FLAC';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const mapStateToProps = state => ({
|
||||
acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']),
|
||||
@ -60,11 +58,13 @@ class UploadButton extends ImmutablePureComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
const message = intl.formatMessage(messages.upload);
|
||||
|
||||
return (
|
||||
<div className='compose-form__upload-button'>
|
||||
<IconButton icon='paperclip' title={intl.formatMessage(messages.upload, { formats: SUPPORTED_FORMATS })} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
||||
<IconButton icon='paperclip' title={message} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
||||
<label>
|
||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload, { formats: SUPPORTED_FORMATS })}</span>
|
||||
<span style={{ display: 'none' }}>{message}</span>
|
||||
<input
|
||||
key={resetFileKey}
|
||||
ref={this.setRef}
|
||||
|
@ -201,10 +201,6 @@ class ActionBar extends React.PureComponent {
|
||||
if (me === status.getIn(['account', 'id'])) {
|
||||
if (publicStatus) {
|
||||
menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
|
||||
} else {
|
||||
if (status.get('visibility') === 'private') {
|
||||
menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
|
||||
}
|
||||
}
|
||||
|
||||
menu.push(null);
|
||||
@ -261,14 +257,23 @@ class ActionBar extends React.PureComponent {
|
||||
replyIcon = 'reply-all';
|
||||
}
|
||||
|
||||
let reblogIcon = 'retweet';
|
||||
if (status.get('visibility') === 'direct') reblogIcon = 'envelope';
|
||||
else if (status.get('visibility') === 'private') reblogIcon = 'lock';
|
||||
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility') === 'private';
|
||||
|
||||
let reblogTitle;
|
||||
if (status.get('reblogged')) {
|
||||
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
|
||||
} else if (publicStatus) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog);
|
||||
} else if (reblogPrivate) {
|
||||
reblogTitle = intl.formatMessage(messages.reblog_private);
|
||||
} else {
|
||||
reblogTitle = intl.formatMessage(messages.cannot_reblog);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='detailed-status__action-bar'>
|
||||
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} onClick={this.handleReplyClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={!publicStatus} active={status.get('reblogged')} title={!publicStatus ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(messages.reblog)} icon={reblogIcon} onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' onClick={this.handleReblogClick} /></div>
|
||||
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /></div>
|
||||
{shareButton}
|
||||
<div className='detailed-status__button'><IconButton className='bookmark-icon' active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /></div>
|
||||
|
@ -191,7 +191,9 @@ export default class Card extends React.PureComponent {
|
||||
this.setState({ previewLoaded: true });
|
||||
}
|
||||
|
||||
handleReveal = () => {
|
||||
handleReveal = e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.setState({ revealed: true });
|
||||
}
|
||||
|
||||
@ -279,7 +281,7 @@ export default class Card extends React.PureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className} ref={this.setRef}>
|
||||
<div className={className} ref={this.setRef} onClick={revealed ? null : this.handleReveal} role={revealed ? 'button' : null}>
|
||||
{embed}
|
||||
{!compact && description}
|
||||
</div>
|
||||
@ -289,14 +291,12 @@ export default class Card extends React.PureComponent {
|
||||
<div className='status-card__image'>
|
||||
{canvas}
|
||||
{thumbnail}
|
||||
{!revealed && spoilerButton}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
embed = (
|
||||
<div className='status-card__image'>
|
||||
<Icon id='file-text' />
|
||||
{!revealed && spoilerButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -305,6 +305,7 @@ export default class Card extends React.PureComponent {
|
||||
<a href={card.get('url')} className={className} target='_blank' rel='noopener noreferrer' ref={this.setRef}>
|
||||
{embed}
|
||||
{description}
|
||||
{!revealed && spoilerButton}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import DisplayName from '../../../components/display_name';
|
||||
import StatusContent from '../../../components/status_content';
|
||||
import MediaGallery from '../../../components/media_gallery';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FormattedDate } from 'react-intl';
|
||||
import { injectIntl, defineMessages, FormattedDate } from 'react-intl';
|
||||
import Card from './card';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Video from '../../video';
|
||||
@ -16,7 +16,15 @@ import classNames from 'classnames';
|
||||
import Icon from 'mastodon/components/icon';
|
||||
import AnimatedNumber from 'mastodon/components/animated_number';
|
||||
|
||||
export default class DetailedStatus extends ImmutablePureComponent {
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||
});
|
||||
|
||||
export default @injectIntl
|
||||
class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
@ -92,7 +100,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
render () {
|
||||
const status = (this.props.status && this.props.status.get('reblog')) ? this.props.status.get('reblog') : this.props.status;
|
||||
const outerStyle = { boxSizing: 'border-box' };
|
||||
const { compact } = this.props;
|
||||
const { intl, compact } = this.props;
|
||||
|
||||
if (!status) {
|
||||
return null;
|
||||
@ -157,34 +165,44 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
if (status.get('application')) {
|
||||
applicationLink = <span> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></span>;
|
||||
applicationLink = <React.Fragment> · <a className='detailed-status__application' href={status.getIn(['application', 'website'])} target='_blank' rel='noopener noreferrer'>{status.getIn(['application', 'name'])}</a></React.Fragment>;
|
||||
}
|
||||
|
||||
if (status.get('visibility') === 'direct') {
|
||||
reblogIcon = 'envelope';
|
||||
} else if (status.get('visibility') === 'private') {
|
||||
reblogIcon = 'lock';
|
||||
}
|
||||
const visibilityIconInfo = {
|
||||
'public': { icon: 'globe', text: intl.formatMessage(messages.public_short) },
|
||||
'unlisted': { icon: 'unlock', text: intl.formatMessage(messages.unlisted_short) },
|
||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||
'direct': { icon: 'envelope', text: intl.formatMessage(messages.direct_short) },
|
||||
};
|
||||
|
||||
const visibilityIcon = visibilityIconInfo[status.get('visibility')];
|
||||
const visibilityLink = <React.Fragment> · <Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></React.Fragment>;
|
||||
|
||||
if (['private', 'direct'].includes(status.get('visibility'))) {
|
||||
reblogLink = <Icon id={reblogIcon} />;
|
||||
reblogLink = '';
|
||||
} else if (this.context.router) {
|
||||
reblogLink = (
|
||||
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</Link>
|
||||
<React.Fragment>
|
||||
<React.Fragment> · </React.Fragment>
|
||||
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
reblogLink = (
|
||||
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</a>
|
||||
<React.Fragment>
|
||||
<React.Fragment> · </React.Fragment>
|
||||
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||
<Icon id={reblogIcon} />
|
||||
<span className='detailed-status__reblogs'>
|
||||
<AnimatedNumber value={status.get('reblogs_count')} />
|
||||
</span>
|
||||
</a>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@ -210,7 +228,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
|
||||
return (
|
||||
<div style={outerStyle}>
|
||||
<div ref={this.setRef} className={classNames('detailed-status', { compact })}>
|
||||
<div ref={this.setRef} className={classNames('detailed-status', `detailed-status-${status.get('visibility')}`, { compact })}>
|
||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
||||
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
||||
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||
@ -223,7 +241,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||
<div className='detailed-status__meta'>
|
||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener noreferrer'>
|
||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
||||
</a>{applicationLink} · {reblogLink} · {favouriteLink}
|
||||
</a>{visibilityLink}{applicationLink}{reblogLink} · {favouriteLink}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -177,15 +177,26 @@ class Video extends React.PureComponent {
|
||||
|
||||
handlePlay = () => {
|
||||
this.setState({ paused: false });
|
||||
this._updateTime();
|
||||
}
|
||||
|
||||
handlePause = () => {
|
||||
this.setState({ paused: true });
|
||||
}
|
||||
|
||||
_updateTime () {
|
||||
requestAnimationFrame(() => {
|
||||
this.handleTimeUpdate();
|
||||
|
||||
if (!this.state.paused) {
|
||||
this._updateTime();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleTimeUpdate = () => {
|
||||
this.setState({
|
||||
currentTime: Math.floor(this.video.currentTime),
|
||||
currentTime: this.video.currentTime,
|
||||
duration: Math.floor(this.video.duration),
|
||||
});
|
||||
}
|
||||
@ -217,7 +228,7 @@ class Video extends React.PureComponent {
|
||||
this.video.volume = x;
|
||||
});
|
||||
}
|
||||
}, 60);
|
||||
}, 15);
|
||||
|
||||
handleMouseDown = e => {
|
||||
document.addEventListener('mousemove', this.handleMouseMove, true);
|
||||
@ -245,13 +256,14 @@ class Video extends React.PureComponent {
|
||||
|
||||
handleMouseMove = throttle(e => {
|
||||
const { x } = getPointerPosition(this.seek, e);
|
||||
const currentTime = Math.floor(this.video.duration * x);
|
||||
const currentTime = this.video.duration * x;
|
||||
|
||||
if (!isNaN(currentTime)) {
|
||||
this.video.currentTime = currentTime;
|
||||
this.setState({ currentTime });
|
||||
this.setState({ currentTime }, () => {
|
||||
this.video.currentTime = currentTime;
|
||||
});
|
||||
}
|
||||
}, 60);
|
||||
}, 15);
|
||||
|
||||
togglePlay = () => {
|
||||
if (this.state.paused) {
|
||||
@ -387,8 +399,10 @@ class Video extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleProgress = () => {
|
||||
if (this.video.buffered.length > 0) {
|
||||
this.setState({ buffer: this.video.buffered.end(0) / this.video.duration * 100 });
|
||||
const lastTimeRange = this.video.buffered.length - 1;
|
||||
|
||||
if (lastTimeRange > -1) {
|
||||
this.setState({ buffer: Math.ceil(this.video.buffered.end(lastTimeRange) / this.video.duration * 100) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,7 +498,6 @@ class Video extends React.PureComponent {
|
||||
onClick={this.togglePlay}
|
||||
onPlay={this.handlePlay}
|
||||
onPause={this.handlePause}
|
||||
onTimeUpdate={this.handleTimeUpdate}
|
||||
onLoadedData={this.handleLoadedData}
|
||||
onProgress={this.handleProgress}
|
||||
onVolumeChange={this.handleVolumeChange}
|
||||
@ -525,7 +538,7 @@ class Video extends React.PureComponent {
|
||||
|
||||
{(detailed || fullscreen) && (
|
||||
<span className='video-player__time'>
|
||||
<span className='video-player__time-current'>{formatTime(currentTime)}</span>
|
||||
<span className='video-player__time-current'>{formatTime(Math.floor(currentTime))}</span>
|
||||
<span className='video-player__time-sep'>/</span>
|
||||
<span className='video-player__time-total'>{formatTime(duration)}</span>
|
||||
</span>
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "El borrador va perdese si coles de Mastodon.",
|
||||
"upload_area.title": "Arrastra y suelta pa xubir",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "La xuba de ficheros nun ta permitida con encuestes.",
|
||||
"upload_form.audio_description": "Descripción pa persones con perda auditiva",
|
||||
|
@ -1206,7 +1206,7 @@
|
||||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "Add media ({formats})",
|
||||
"defaultMessage": "Add images, a video or an audio file",
|
||||
"id": "upload_button.label"
|
||||
}
|
||||
],
|
||||
|
@ -427,7 +427,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -422,7 +422,7 @@
|
||||
"trends.trending_now": "Trending now",
|
||||
"ui.beforeunload": "Your draft will be lost if you leave Mastodon.",
|
||||
"upload_area.title": "Drag & drop to upload",
|
||||
"upload_button.label": "Add media ({formats})",
|
||||
"upload_button.label": "Add images, a video or an audio file",
|
||||
"upload_error.limit": "File upload limit exceeded.",
|
||||
"upload_error.poll": "File upload not allowed with polls.",
|
||||
"upload_form.audio_description": "Describe for people with hearing loss",
|
||||
|
@ -171,9 +171,7 @@ $content-width: 840px;
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 20px 15px;
|
||||
padding-top: 60px;
|
||||
padding-left: 25px;
|
||||
padding: 55px 15px 20px 25px;
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
max-width: none;
|
||||
@ -184,7 +182,7 @@ $content-width: 840px;
|
||||
&-heading {
|
||||
display: flex;
|
||||
|
||||
padding-bottom: 40px;
|
||||
padding-bottom: 36px;
|
||||
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||
|
||||
margin: -15px -15px 40px 0;
|
||||
@ -215,7 +213,7 @@ $content-width: 840px;
|
||||
h2 {
|
||||
color: $secondary-text-color;
|
||||
font-size: 24px;
|
||||
line-height: 28px;
|
||||
line-height: 36px;
|
||||
font-weight: 400;
|
||||
|
||||
@media screen and (max-width: $no-columns-breakpoint) {
|
||||
@ -544,6 +542,16 @@ body,
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.simple_form {
|
||||
.actions {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
.batch-form-box {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
@ -68,7 +68,32 @@ body {
|
||||
}
|
||||
|
||||
&.player {
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& > div {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-player video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.media-gallery {
|
||||
margin-top: 0;
|
||||
height: 100% !important;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.media-gallery__item {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.embed {
|
||||
|
@ -1019,7 +1019,8 @@
|
||||
}
|
||||
|
||||
&.light {
|
||||
.status__relative-time {
|
||||
.status__relative-time,
|
||||
.status__visibility-icon {
|
||||
color: $light-text-color;
|
||||
}
|
||||
|
||||
@ -1065,12 +1066,18 @@
|
||||
}
|
||||
|
||||
.status__relative-time,
|
||||
.status__visibility-icon,
|
||||
.notification__relative_time {
|
||||
color: $dark-text-color;
|
||||
float: right;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.status__visibility-icon {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.status__display-name {
|
||||
color: $dark-text-color;
|
||||
}
|
||||
@ -3003,6 +3010,7 @@ a.account__display-name {
|
||||
}
|
||||
|
||||
.status-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
font-size: 14px;
|
||||
border: 1px solid lighten($ui-base-color, 8%);
|
||||
|
@ -158,6 +158,7 @@ body.rtl {
|
||||
}
|
||||
|
||||
.status__relative-time,
|
||||
.status__visibility-icon,
|
||||
.activity-stream .status.light .status__header .status__meta {
|
||||
float: left;
|
||||
}
|
||||
|
@ -140,6 +140,11 @@
|
||||
|
||||
.detailed-status {
|
||||
padding: 15px;
|
||||
|
||||
.detailed-status__display-avatar .account__avatar {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
|
Reference in New Issue
Block a user