[Glitch] Redesign public hashtag page to use a masonry layout
Port 380b246728
to glitch flavour
lolsob-rspec
parent
b49d415f37
commit
8219940214
|
@ -9,15 +9,23 @@ export default function DisplayName ({
|
||||||
account,
|
account,
|
||||||
className,
|
className,
|
||||||
inline,
|
inline,
|
||||||
|
localDomain,
|
||||||
}) {
|
}) {
|
||||||
const computedClass = classNames('display-name', { inline }, className);
|
const computedClass = classNames('display-name', { inline }, className);
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
let acct = account.get('acct');
|
||||||
|
if (acct.indexOf('@') === -1 && localDomain) {
|
||||||
|
acct = `${acct}@${localDomain}`;
|
||||||
|
}
|
||||||
|
|
||||||
// The result.
|
// The result.
|
||||||
return account ? (
|
return account ? (
|
||||||
<span className={computedClass}>
|
<span className={computedClass}>
|
||||||
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
<bdi><strong className='display-name__html' dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }} /></bdi>
|
||||||
{inline ? ' ' : null}
|
{inline ? ' ' : null}
|
||||||
<span className='display-name__account'>@{account.get('acct')}</span>
|
<span className='display-name__account'>@{acct}</span>
|
||||||
</span>
|
</span>
|
||||||
) : null;
|
) : null;
|
||||||
}
|
}
|
||||||
|
@ -27,4 +35,5 @@ DisplayName.propTypes = {
|
||||||
account: ImmutablePropTypes.map,
|
account: ImmutablePropTypes.map,
|
||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
inline: PropTypes.bool,
|
inline: PropTypes.bool,
|
||||||
|
localDomain: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,28 +1,32 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
|
import { expandHashtagTimeline } from 'flavours/glitch/actions/timelines';
|
||||||
import Column from 'flavours/glitch/components/column';
|
|
||||||
import ColumnHeader from 'flavours/glitch/components/column_header';
|
|
||||||
import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
|
import { connectHashtagStream } from 'flavours/glitch/actions/streaming';
|
||||||
|
import Masonry from 'react-masonry-infinite';
|
||||||
|
import { List as ImmutableList } from 'immutable';
|
||||||
|
import DetailedStatusContainer from 'flavours/glitch/features/status/containers/detailed_status_container';
|
||||||
|
import { debounce } from 'lodash';
|
||||||
|
import LoadingIndicator from 'flavours/glitch/components/loading_indicator';
|
||||||
|
|
||||||
@connect()
|
const mapStateToProps = (state, { hashtag }) => ({
|
||||||
export default class HashtagTimeline extends React.PureComponent {
|
statusIds: state.getIn(['timelines', `hashtag:${hashtag}`, 'items'], ImmutableList()),
|
||||||
|
isLoading: state.getIn(['timelines', `hashtag:${hashtag}`, 'isLoading'], false),
|
||||||
|
hasMore: state.getIn(['timelines', `hashtag:${hashtag}`, 'hasMore'], false),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class HashtagTimeline extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
statusIds: ImmutablePropTypes.list.isRequired,
|
||||||
|
isLoading: PropTypes.bool.isRequired,
|
||||||
|
hasMore: PropTypes.bool.isRequired,
|
||||||
hashtag: PropTypes.string.isRequired,
|
hashtag: PropTypes.string.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleHeaderClick = () => {
|
|
||||||
this.column.scrollTop();
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = c => {
|
|
||||||
this.column = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
const { dispatch, hashtag } = this.props;
|
const { dispatch, hashtag } = this.props;
|
||||||
|
|
||||||
|
@ -37,28 +41,52 @@ export default class HashtagTimeline extends React.PureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = () => {
|
||||||
|
const maxId = this.props.statusIds.last();
|
||||||
|
|
||||||
|
if (maxId) {
|
||||||
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
this.props.dispatch(expandHashtagTimeline(this.props.hashtag, { maxId }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.masonry = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleHeightChange = debounce(() => {
|
||||||
|
if (!this.masonry) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masonry.forcePack();
|
||||||
|
}, 50)
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { hashtag } = this.props;
|
const { statusIds, hasMore, isLoading } = this.props;
|
||||||
|
|
||||||
|
const sizes = [
|
||||||
|
{ columns: 1, gutter: 0 },
|
||||||
|
{ mq: '415px', columns: 1, gutter: 10 },
|
||||||
|
{ mq: '640px', columns: 2, gutter: 10 },
|
||||||
|
{ mq: '960px', columns: 3, gutter: 10 },
|
||||||
|
{ mq: '1255px', columns: 3, gutter: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const loader = (isLoading && statusIds.isEmpty()) ? <LoadingIndicator key={0} /> : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column ref={this.setRef}>
|
<Masonry ref={this.setRef} className='statuses-grid' hasMore={hasMore} loadMore={this.handleLoadMore} sizes={sizes} loader={loader}>
|
||||||
<ColumnHeader
|
{statusIds.map(statusId => (
|
||||||
icon='hashtag'
|
<div className='statuses-grid__item' key={statusId}>
|
||||||
title={hashtag}
|
<DetailedStatusContainer
|
||||||
onClick={this.handleHeaderClick}
|
id={statusId}
|
||||||
|
showThread
|
||||||
|
measureHeight
|
||||||
|
onHeightChange={this.handleHeightChange}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<StatusListContainer
|
)).toArray()}
|
||||||
trackScroll={false}
|
</Masonry>
|
||||||
scrollKey='standalone_hashtag_timeline'
|
|
||||||
timelineId={`hashtag:${hashtag}`}
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import Card from './card';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import Video from 'flavours/glitch/features/video';
|
import Video from 'flavours/glitch/features/video';
|
||||||
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
||||||
|
import scheduleIdleTask from 'flavours/glitch/util/schedule_idle_task';
|
||||||
|
|
||||||
export default class DetailedStatus extends ImmutablePureComponent {
|
export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
@ -26,10 +27,17 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
onOpenVideo: PropTypes.func.isRequired,
|
onOpenVideo: PropTypes.func.isRequired,
|
||||||
onToggleHidden: PropTypes.func.isRequired,
|
onToggleHidden: PropTypes.func.isRequired,
|
||||||
expanded: PropTypes.bool,
|
expanded: PropTypes.bool,
|
||||||
|
measureHeight: PropTypes.bool,
|
||||||
|
onHeightChange: PropTypes.func,
|
||||||
|
domain: PropTypes.string.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
height: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleAccountClick = (e) => {
|
handleAccountClick = (e) => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
|
||||||
}
|
}
|
||||||
|
@ -38,7 +46,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
parseClick = (e, destination) => {
|
parseClick = (e, destination) => {
|
||||||
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey)) {
|
if (e.button === 0 && !(e.ctrlKey || e.altKey || e.metaKey) && this.context.router) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.context.router.history.push(destination);
|
this.context.router.history.push(destination);
|
||||||
}
|
}
|
||||||
|
@ -50,15 +58,58 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
this.props.onOpenVideo(media, startTime);
|
this.props.onOpenVideo(media, startTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_measureHeight (heightJustChanged) {
|
||||||
|
if (this.props.measureHeight && this.node) {
|
||||||
|
scheduleIdleTask(() => this.node && this.setState({ height: this.node.offsetHeight }));
|
||||||
|
|
||||||
|
if (this.props.onHeightChange && heightJustChanged) {
|
||||||
|
this.props.onHeightChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = c => {
|
||||||
|
this.node = c;
|
||||||
|
this._measureHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate (prevProps, prevState) {
|
||||||
|
this._measureHeight(prevState.height !== this.state.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleModalLink = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
let href;
|
||||||
|
|
||||||
|
if (e.target.nodeName !== 'A') {
|
||||||
|
href = e.target.parentNode.href;
|
||||||
|
} else {
|
||||||
|
href = e.target.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.open(href, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes');
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
const status = this.props.status.get('reblog') ? this.props.status.get('reblog') : this.props.status;
|
||||||
const { expanded, onToggleHidden, settings } = this.props;
|
const { expanded, onToggleHidden, settings } = this.props;
|
||||||
|
const outerStyle = { boxSizing: 'border-box' };
|
||||||
|
|
||||||
|
if (!status) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let media = '';
|
let media = '';
|
||||||
let mediaIcon = null;
|
let mediaIcon = null;
|
||||||
let applicationLink = '';
|
let applicationLink = '';
|
||||||
let reblogLink = '';
|
let reblogLink = '';
|
||||||
let reblogIcon = 'retweet';
|
let reblogIcon = 'retweet';
|
||||||
|
let favouriteLink = '';
|
||||||
|
|
||||||
|
if (this.props.measureHeight) {
|
||||||
|
outerStyle.height = `${this.state.height}px`;
|
||||||
|
}
|
||||||
|
|
||||||
if (status.get('media_attachments').size > 0) {
|
if (status.get('media_attachments').size > 0) {
|
||||||
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
if (status.get('media_attachments').some(item => item.get('type') === 'unknown')) {
|
||||||
|
@ -108,20 +159,51 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (status.get('visibility') === 'private') {
|
if (status.get('visibility') === 'private') {
|
||||||
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
reblogLink = <i className={`fa fa-${reblogIcon}`} />;
|
||||||
} else {
|
} else if (this.context.router) {
|
||||||
reblogLink = (<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
reblogLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/reblogs`} className='detailed-status__link'>
|
||||||
<i className={`fa fa-${reblogIcon}`} />
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
<span className='detailed-status__reblogs'>
|
<span className='detailed-status__reblogs'>
|
||||||
<FormattedNumber value={status.get('reblogs_count')} />
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
</span>
|
</span>
|
||||||
</Link>);
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
reblogLink = (
|
||||||
|
<a href={`/interact/${status.get('id')}?type=reblog`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
|
<i className={`fa fa-${reblogIcon}`} />
|
||||||
|
<span className='detailed-status__reblogs'>
|
||||||
|
<FormattedNumber value={status.get('reblogs_count')} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.context.router) {
|
||||||
|
favouriteLink = (
|
||||||
|
<Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
favouriteLink = (
|
||||||
|
<a href={`/interact/${status.get('id')}?type=favourite`} className='detailed-status__link' onClick={this.handleModalLink}>
|
||||||
|
<i className='fa fa-star' />
|
||||||
|
<span className='detailed-status__favorites'>
|
||||||
|
<FormattedNumber value={status.get('favourites_count')} />
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='detailed-status' data-status-by={status.getIn(['account', 'acct'])}>
|
<div ref={this.setRef} className='detailed-status' data-status-by={status.getIn(['account', 'acct'])} style={outerStyle}>
|
||||||
<a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='detailed-status__display-name'>
|
<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>
|
<div className='detailed-status__display-avatar'><Avatar account={status.get('account')} size={48} /></div>
|
||||||
<DisplayName account={status.get('account')} />
|
<DisplayName account={status.get('account')} localDomain={this.props.domain} />
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<StatusContent
|
<StatusContent
|
||||||
|
@ -137,12 +219,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
|
||||||
<div className='detailed-status__meta'>
|
<div className='detailed-status__meta'>
|
||||||
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
<a className='detailed-status__datetime' href={status.get('url')} target='_blank' rel='noopener'>
|
||||||
<FormattedDate value={new Date(status.get('created_at'))} hour12={false} year='numeric' month='short' day='2-digit' hour='2-digit' minute='2-digit' />
|
<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} · <Link to={`/statuses/${status.get('id')}/favourites`} className='detailed-status__link'>
|
</a>{applicationLink} · {reblogLink} · {favouriteLink} · <VisibilityIcon visibility={status.get('visibility')} />
|
||||||
<i className='fa fa-star' />
|
|
||||||
<span className='detailed-status__favorites'>
|
|
||||||
<FormattedNumber value={status.get('favourites_count')} />
|
|
||||||
</span>
|
|
||||||
</Link> · <VisibilityIcon visibility={status.get('visibility')} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import DetailedStatus from '../components/detailed_status';
|
||||||
|
import { makeGetStatus } from 'flavours/glitch/selectors';
|
||||||
|
import {
|
||||||
|
replyCompose,
|
||||||
|
mentionCompose,
|
||||||
|
directCompose,
|
||||||
|
} from 'flavours/glitch/actions/compose';
|
||||||
|
import {
|
||||||
|
reblog,
|
||||||
|
favourite,
|
||||||
|
unreblog,
|
||||||
|
unfavourite,
|
||||||
|
pin,
|
||||||
|
unpin,
|
||||||
|
} from 'flavours/glitch/actions/interactions';
|
||||||
|
import { blockAccount } from 'flavours/glitch/actions/accounts';
|
||||||
|
import {
|
||||||
|
muteStatus,
|
||||||
|
unmuteStatus,
|
||||||
|
deleteStatus,
|
||||||
|
hideStatus,
|
||||||
|
revealStatus,
|
||||||
|
} from 'flavours/glitch/actions/statuses';
|
||||||
|
import { initMuteModal } from 'flavours/glitch/actions/mutes';
|
||||||
|
import { initReport } from 'flavours/glitch/actions/reports';
|
||||||
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||||
|
import { boostModal, deleteModal } from 'flavours/glitch/util/initial_state';
|
||||||
|
import { showAlertForError } from 'flavours/glitch/actions/alerts';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
|
||||||
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
|
||||||
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const makeMapStateToProps = () => {
|
||||||
|
const getStatus = makeGetStatus();
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
status: getStatus(state, props),
|
||||||
|
domain: state.getIn(['meta', 'domain']),
|
||||||
|
settings: state.get('local_settings'),
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapStateToProps;
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||||
|
|
||||||
|
onReply (status, router) {
|
||||||
|
dispatch((_, getState) => {
|
||||||
|
let state = getState();
|
||||||
|
if (state.getIn(['compose', 'text']).trim().length !== 0) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(messages.replyMessage),
|
||||||
|
confirm: intl.formatMessage(messages.replyConfirm),
|
||||||
|
onConfirm: () => dispatch(replyCompose(status, router)),
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
dispatch(replyCompose(status, router));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onModalReblog (status) {
|
||||||
|
dispatch(reblog(status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReblog (status, e) {
|
||||||
|
if (status.get('reblogged')) {
|
||||||
|
dispatch(unreblog(status));
|
||||||
|
} else {
|
||||||
|
if (e.shiftKey || !boostModal) {
|
||||||
|
this.onModalReblog(status);
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onFavourite (status) {
|
||||||
|
if (status.get('favourited')) {
|
||||||
|
dispatch(unfavourite(status));
|
||||||
|
} else {
|
||||||
|
dispatch(favourite(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onPin (status) {
|
||||||
|
if (status.get('pinned')) {
|
||||||
|
dispatch(unpin(status));
|
||||||
|
} else {
|
||||||
|
dispatch(pin(status));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onEmbed (status) {
|
||||||
|
dispatch(openModal('EMBED', {
|
||||||
|
url: status.get('url'),
|
||||||
|
onError: error => dispatch(showAlertForError(error)),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onDelete (status, history, withRedraft = false) {
|
||||||
|
if (!deleteModal) {
|
||||||
|
dispatch(deleteStatus(status.get('id'), history, withRedraft));
|
||||||
|
} else {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
|
||||||
|
confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
|
||||||
|
onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onDirect (account, router) {
|
||||||
|
dispatch(directCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMention (account, router) {
|
||||||
|
dispatch(mentionCompose(account, router));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenMedia (media, index) {
|
||||||
|
dispatch(openModal('MEDIA', { media, index }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onOpenVideo (media, time) {
|
||||||
|
dispatch(openModal('VIDEO', { media, time }));
|
||||||
|
},
|
||||||
|
|
||||||
|
onBlock (account) {
|
||||||
|
dispatch(openModal('CONFIRM', {
|
||||||
|
message: <FormattedMessage id='confirmations.block.message' defaultMessage='Are you sure you want to block {name}?' values={{ name: <strong>@{account.get('acct')}</strong> }} />,
|
||||||
|
confirm: intl.formatMessage(messages.blockConfirm),
|
||||||
|
onConfirm: () => dispatch(blockAccount(account.get('id'))),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
|
||||||
|
onReport (status) {
|
||||||
|
dispatch(initReport(status.get('account'), status));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
dispatch(initMuteModal(account));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMuteConversation (status) {
|
||||||
|
if (status.get('muted')) {
|
||||||
|
dispatch(unmuteStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(muteStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onToggleHidden (status) {
|
||||||
|
if (status.get('hidden')) {
|
||||||
|
dispatch(revealStatus(status.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(hideStatus(status.get('id')));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(DetailedStatus));
|
|
@ -425,3 +425,30 @@
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$maximum-width: 1235px;
|
||||||
|
$fluid-breakpoint: $maximum-width + 20px;
|
||||||
|
|
||||||
|
.statuses-grid {
|
||||||
|
min-height: 600px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
width: (960px - 20px) / 3;
|
||||||
|
|
||||||
|
@media screen and (max-width: $fluid-breakpoint) {
|
||||||
|
width: (940px - 20px) / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailed-status {
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
@media screen and (max-width: $no-gap-breakpoint) {
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 12%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue