Add account media gallery view to web UI (#3120)
* Add account media gallery view to web UI * Link media view from account dropdown * Adjust linklolsob-rspec
parent
eeb78b394c
commit
94b26e90fa
|
@ -37,6 +37,14 @@ export const ACCOUNT_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_TIMELINE_EXPAND_REQUEST'
|
||||||
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
|
export const ACCOUNT_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_TIMELINE_EXPAND_SUCCESS';
|
||||||
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
|
export const ACCOUNT_TIMELINE_EXPAND_FAIL = 'ACCOUNT_TIMELINE_EXPAND_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST';
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS';
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL = 'ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST';
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS';
|
||||||
|
export const ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL = 'ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL';
|
||||||
|
|
||||||
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST';
|
||||||
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||||
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL';
|
||||||
|
@ -96,17 +104,17 @@ export function fetchAccountTimeline(id, replace = false) {
|
||||||
const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
|
const ids = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List());
|
||||||
const newestId = ids.size > 0 ? ids.first() : null;
|
const newestId = ids.size > 0 ? ids.first() : null;
|
||||||
|
|
||||||
let params = '';
|
let params = {};
|
||||||
let skipLoading = false;
|
let skipLoading = false;
|
||||||
|
|
||||||
if (newestId !== null && !replace) {
|
if (newestId !== null && !replace) {
|
||||||
params = `?since_id=${newestId}`;
|
params.since_id = newestId;
|
||||||
skipLoading = true;
|
skipLoading = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(fetchAccountTimelineRequest(id, skipLoading));
|
dispatch(fetchAccountTimelineRequest(id, skipLoading));
|
||||||
|
|
||||||
api(getState).get(`/api/v1/accounts/${id}/statuses${params}`).then(response => {
|
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
||||||
dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
|
dispatch(fetchAccountTimelineSuccess(id, response.data, replace, skipLoading));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(fetchAccountTimelineFail(id, error, skipLoading));
|
dispatch(fetchAccountTimelineFail(id, error, skipLoading));
|
||||||
|
@ -114,6 +122,29 @@ export function fetchAccountTimeline(id, replace = false) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function fetchAccountMediaTimeline(id, replace = false) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const ids = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List());
|
||||||
|
const newestId = ids.size > 0 ? ids.first() : null;
|
||||||
|
|
||||||
|
let params = { only_media: 'true', limit: 12 };
|
||||||
|
let skipLoading = false;
|
||||||
|
|
||||||
|
if (newestId !== null && !replace) {
|
||||||
|
params.since_id = newestId;
|
||||||
|
skipLoading = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatch(fetchAccountMediaTimelineRequest(id, skipLoading));
|
||||||
|
|
||||||
|
api(getState).get(`/api/v1/accounts/${id}/statuses`, { params }).then(response => {
|
||||||
|
dispatch(fetchAccountMediaTimelineSuccess(id, response.data, replace, skipLoading));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(fetchAccountMediaTimelineFail(id, error, skipLoading));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function expandAccountTimeline(id) {
|
export function expandAccountTimeline(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
|
const lastId = getState().getIn(['timelines', 'accounts_timelines', id, 'items'], Immutable.List()).last();
|
||||||
|
@ -134,6 +165,27 @@ export function expandAccountTimeline(id) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function expandAccountMediaTimeline(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
const lastId = getState().getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List()).last();
|
||||||
|
|
||||||
|
dispatch(expandAccountMediaTimelineRequest(id));
|
||||||
|
|
||||||
|
api(getState).get(`/api/v1/accounts/${id}/statuses`, {
|
||||||
|
params: {
|
||||||
|
limit: 12,
|
||||||
|
only_media: 'true',
|
||||||
|
max_id: lastId
|
||||||
|
}
|
||||||
|
}).then(response => {
|
||||||
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
dispatch(expandAccountMediaTimelineSuccess(id, response.data, next));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(expandAccountMediaTimelineFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function fetchAccountRequest(id) {
|
export function fetchAccountRequest(id) {
|
||||||
return {
|
return {
|
||||||
type: ACCOUNT_FETCH_REQUEST,
|
type: ACCOUNT_FETCH_REQUEST,
|
||||||
|
@ -251,6 +303,34 @@ export function fetchAccountTimelineFail(id, error, skipLoading) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function fetchAccountMediaTimelineRequest(id, skipLoading) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST,
|
||||||
|
id,
|
||||||
|
skipLoading
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchAccountMediaTimelineSuccess(id, statuses, replace, skipLoading) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
||||||
|
id,
|
||||||
|
statuses,
|
||||||
|
replace,
|
||||||
|
skipLoading
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchAccountMediaTimelineFail(id, error, skipLoading) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL,
|
||||||
|
id,
|
||||||
|
error,
|
||||||
|
skipLoading,
|
||||||
|
skipAlert: error.response.status === 404
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function expandAccountTimelineRequest(id) {
|
export function expandAccountTimelineRequest(id) {
|
||||||
return {
|
return {
|
||||||
type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
type: ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
||||||
|
@ -275,6 +355,30 @@ export function expandAccountTimelineFail(id, error) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function expandAccountMediaTimelineRequest(id) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function expandAccountMediaTimelineSuccess(id, statuses, next) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
||||||
|
id,
|
||||||
|
statuses,
|
||||||
|
next
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function expandAccountMediaTimelineFail(id, error) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL,
|
||||||
|
id,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export function blockAccount(id) {
|
export function blockAccount(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(blockAccountRequest(id));
|
dispatch(blockAccountRequest(id));
|
||||||
|
|
|
@ -4,6 +4,10 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
class DropdownMenu extends React.PureComponent {
|
class DropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
router: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
icon: PropTypes.string.isRequired,
|
icon: PropTypes.string.isRequired,
|
||||||
items: PropTypes.array.isRequired,
|
items: PropTypes.array.isRequired,
|
||||||
|
@ -26,13 +30,17 @@ class DropdownMenu extends React.PureComponent {
|
||||||
|
|
||||||
handleClick = (e) => {
|
handleClick = (e) => {
|
||||||
const i = Number(e.currentTarget.getAttribute('data-index'));
|
const i = Number(e.currentTarget.getAttribute('data-index'));
|
||||||
const { action } = this.props.items[i];
|
const { action, to } = this.props.items[i];
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
if (typeof action === 'function') {
|
if (typeof action === 'function') {
|
||||||
e.preventDefault();
|
|
||||||
action();
|
action();
|
||||||
this.dropdown.hide();
|
} else if (to) {
|
||||||
|
this.context.router.push(to);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dropdown.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = (item, i) => {
|
renderItem = (item, i) => {
|
||||||
|
|
|
@ -26,6 +26,7 @@ import GettingStarted from '../features/getting_started';
|
||||||
import PublicTimeline from '../features/public_timeline';
|
import PublicTimeline from '../features/public_timeline';
|
||||||
import CommunityTimeline from '../features/community_timeline';
|
import CommunityTimeline from '../features/community_timeline';
|
||||||
import AccountTimeline from '../features/account_timeline';
|
import AccountTimeline from '../features/account_timeline';
|
||||||
|
import AccountGallery from '../features/account_gallery';
|
||||||
import HomeTimeline from '../features/home_timeline';
|
import HomeTimeline from '../features/home_timeline';
|
||||||
import Compose from '../features/compose';
|
import Compose from '../features/compose';
|
||||||
import Followers from '../features/followers';
|
import Followers from '../features/followers';
|
||||||
|
@ -204,6 +205,7 @@ class Mastodon extends React.PureComponent {
|
||||||
<Route path='accounts/:accountId' component={AccountTimeline} />
|
<Route path='accounts/:accountId' component={AccountTimeline} />
|
||||||
<Route path='accounts/:accountId/followers' component={Followers} />
|
<Route path='accounts/:accountId/followers' component={Followers} />
|
||||||
<Route path='accounts/:accountId/following' component={Following} />
|
<Route path='accounts/:accountId/following' component={Following} />
|
||||||
|
<Route path='accounts/:accountId/media' component={AccountGallery} />
|
||||||
|
|
||||||
<Route path='follow_requests' component={FollowRequests} />
|
<Route path='follow_requests' component={FollowRequests} />
|
||||||
<Route path='blocks' component={Blocks} />
|
<Route path='blocks' component={Blocks} />
|
||||||
|
|
|
@ -15,6 +15,7 @@ const messages = defineMessages({
|
||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' },
|
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
||||||
|
@ -43,6 +44,8 @@ class ActionBar extends React.PureComponent {
|
||||||
|
|
||||||
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention });
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
|
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
|
||||||
|
menu.push(null);
|
||||||
|
|
||||||
if (account.get('id') === me) {
|
if (account.get('id') === me) {
|
||||||
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
menu.push({ text: intl.formatMessage(messages.edit_profile), href: '/settings/profile' });
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import Permalink from '../../../components/permalink';
|
||||||
|
|
||||||
|
class MediaItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
media: ImmutablePropTypes.map.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { media } = this.props;
|
||||||
|
const status = media.get('status');
|
||||||
|
|
||||||
|
let content, style;
|
||||||
|
|
||||||
|
if (media.get('type') === 'gifv') {
|
||||||
|
content = <span className='media-gallery__gifv__label'>GIF</span>;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status.get('sensitive')) {
|
||||||
|
style = { backgroundImage: `url(${media.get('preview_url')})` };
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account-gallery__item'>
|
||||||
|
<Permalink
|
||||||
|
to={`/statuses/${status.get('id')}`}
|
||||||
|
href={status.get('url')}
|
||||||
|
style={style}>
|
||||||
|
{content}
|
||||||
|
</Permalink>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MediaItem;
|
|
@ -0,0 +1,99 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
fetchAccount,
|
||||||
|
fetchAccountMediaTimeline,
|
||||||
|
expandAccountMediaTimeline
|
||||||
|
} from '../../actions/accounts';
|
||||||
|
import LoadingIndicator from '../../components/loading_indicator';
|
||||||
|
import Column from '../ui/components/column';
|
||||||
|
import ColumnBackButton from '../../components/column_back_button';
|
||||||
|
import Immutable from 'immutable';
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
import { getAccountGallery } from '../../selectors';
|
||||||
|
import MediaItem from './components/media_item';
|
||||||
|
import HeaderContainer from '../account_timeline/containers/header_container';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { ScrollContainer } from 'react-router-scroll';
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
medias: getAccountGallery(state, Number(props.params.accountId)),
|
||||||
|
isLoading: state.getIn(['timelines', 'accounts_media_timelines', Number(props.params.accountId), 'isLoading']),
|
||||||
|
hasMore: !!state.getIn(['timelines', 'accounts_media_timelines', Number(props.params.accountId), 'next']),
|
||||||
|
autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
|
||||||
|
});
|
||||||
|
|
||||||
|
class AccountGallery extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
params: PropTypes.object.isRequired,
|
||||||
|
dispatch: PropTypes.func.isRequired,
|
||||||
|
medias: ImmutablePropTypes.list.isRequired,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
|
hasMore: PropTypes.bool,
|
||||||
|
autoPlayGif: PropTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
this.props.dispatch(fetchAccount(Number(this.props.params.accountId)));
|
||||||
|
this.props.dispatch(fetchAccountMediaTimeline(Number(this.props.params.accountId)));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps (nextProps) {
|
||||||
|
if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) {
|
||||||
|
this.props.dispatch(fetchAccount(Number(nextProps.params.accountId)));
|
||||||
|
this.props.dispatch(fetchAccountMediaTimeline(Number(this.props.params.accountId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll = (e) => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
|
||||||
|
if (scrollTop === scrollHeight - clientHeight) {
|
||||||
|
this.props.dispatch(expandAccountMediaTimeline(Number(this.props.params.accountId)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { medias, autoPlayGif, isLoading } = this.props;
|
||||||
|
|
||||||
|
if (!medias && isLoading) {
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<LoadingIndicator />
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Column>
|
||||||
|
<ColumnBackButton />
|
||||||
|
|
||||||
|
<ScrollContainer scrollKey='account_gallery'>
|
||||||
|
<div className='scrollable' onScroll={this.handleScroll}>
|
||||||
|
<HeaderContainer accountId={this.props.params.accountId} />
|
||||||
|
|
||||||
|
<div className='account-section-headline'>
|
||||||
|
<FormattedMessage id='account.media' defaultMessage='Media' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account-gallery__container'>
|
||||||
|
{medias.map(media =>
|
||||||
|
<MediaItem
|
||||||
|
key={media.get('id')}
|
||||||
|
media={media}
|
||||||
|
autoPlayGif={autoPlayGif}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollContainer>
|
||||||
|
</Column>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(AccountGallery);
|
|
@ -28,16 +28,11 @@ class Blocks extends ImmutablePureComponent {
|
||||||
intl: PropTypes.object.isRequired
|
intl: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor (props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.handleScroll = this.handleScroll.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
this.props.dispatch(fetchBlocks());
|
this.props.dispatch(fetchBlocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll = (e) => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
|
||||||
if (scrollTop === scrollHeight - clientHeight) {
|
if (scrollTop === scrollHeight - clientHeight) {
|
||||||
|
|
|
@ -21,16 +21,11 @@ const mapStateToProps = state => ({
|
||||||
|
|
||||||
class Mutes extends ImmutablePureComponent {
|
class Mutes extends ImmutablePureComponent {
|
||||||
|
|
||||||
constructor (props, context) {
|
|
||||||
super(props, context);
|
|
||||||
this.handleScroll = this.handleScroll.bind(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
componentWillMount () {
|
||||||
this.props.dispatch(fetchMutes());
|
this.props.dispatch(fetchMutes());
|
||||||
}
|
}
|
||||||
|
|
||||||
handleScroll (e) {
|
handleScroll = (e) => {
|
||||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||||
|
|
||||||
if (scrollTop === scrollHeight - clientHeight) {
|
if (scrollTop === scrollHeight - clientHeight) {
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"account.followers": "フォロワー",
|
"account.followers": "フォロワー",
|
||||||
"account.follows": "フォロー",
|
"account.follows": "フォロー",
|
||||||
"account.follows_you": "フォローされています",
|
"account.follows_you": "フォローされています",
|
||||||
"account.media": "Media",
|
"account.media": "メディア",
|
||||||
"account.mention": "返信",
|
"account.mention": "返信",
|
||||||
"account.mute": "ミュート",
|
"account.mute": "ミュート",
|
||||||
"account.posts": "投稿",
|
"account.posts": "投稿",
|
||||||
|
|
|
@ -23,6 +23,8 @@ import {
|
||||||
import {
|
import {
|
||||||
ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
||||||
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
||||||
ACCOUNT_BLOCK_SUCCESS
|
ACCOUNT_BLOCK_SUCCESS
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import {
|
import {
|
||||||
|
@ -113,6 +115,8 @@ export default function statuses(state = initialState, action) {
|
||||||
case TIMELINE_EXPAND_SUCCESS:
|
case TIMELINE_EXPAND_SUCCESS:
|
||||||
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
|
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
|
||||||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS:
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS:
|
||||||
case CONTEXT_FETCH_SUCCESS:
|
case CONTEXT_FETCH_SUCCESS:
|
||||||
case NOTIFICATIONS_REFRESH_SUCCESS:
|
case NOTIFICATIONS_REFRESH_SUCCESS:
|
||||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||||
|
|
|
@ -24,6 +24,12 @@ import {
|
||||||
ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
||||||
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||||
ACCOUNT_TIMELINE_EXPAND_FAIL,
|
ACCOUNT_TIMELINE_EXPAND_FAIL,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
||||||
|
ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL,
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_MUTE_SUCCESS
|
ACCOUNT_MUTE_SUCCESS
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
|
@ -79,6 +85,7 @@ const initialState = Immutable.Map({
|
||||||
}),
|
}),
|
||||||
|
|
||||||
accounts_timelines: Immutable.Map(),
|
accounts_timelines: Immutable.Map(),
|
||||||
|
accounts_media_timelines: Immutable.Map(),
|
||||||
ancestors: Immutable.Map(),
|
ancestors: Immutable.Map(),
|
||||||
descendants: Immutable.Map()
|
descendants: Immutable.Map()
|
||||||
});
|
});
|
||||||
|
@ -148,6 +155,20 @@ const normalizeAccountTimeline = (state, accountId, statuses, replace = false) =
|
||||||
.update('items', Immutable.List(), list => (replace ? ids : ids.concat(list))));
|
.update('items', Immutable.List(), list => (replace ? ids : ids.concat(list))));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const normalizeAccountMediaTimeline = (state, accountId, statuses, next) => {
|
||||||
|
let ids = Immutable.List();
|
||||||
|
|
||||||
|
statuses.forEach((status, i) => {
|
||||||
|
state = normalizeStatus(state, status);
|
||||||
|
ids = ids.set(i, status.get('id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
return state.updateIn(['accounts_media_timelines', accountId], Immutable.Map(), map => map
|
||||||
|
.set('isLoading', false)
|
||||||
|
.set('next', next)
|
||||||
|
.update('items', Immutable.List(), list => ids.concat(list)));
|
||||||
|
};
|
||||||
|
|
||||||
const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
|
const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
|
||||||
let moreIds = Immutable.List([]);
|
let moreIds = Immutable.List([]);
|
||||||
|
|
||||||
|
@ -162,6 +183,20 @@ const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
|
||||||
.update('items', list => list.concat(moreIds)));
|
.update('items', list => list.concat(moreIds)));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const appendNormalizedAccountMediaTimeline = (state, accountId, statuses, next) => {
|
||||||
|
let moreIds = Immutable.List([]);
|
||||||
|
|
||||||
|
statuses.forEach((status, i) => {
|
||||||
|
state = normalizeStatus(state, status);
|
||||||
|
moreIds = moreIds.set(i, status.get('id'));
|
||||||
|
});
|
||||||
|
|
||||||
|
return state.updateIn(['accounts_media_timelines', accountId], Immutable.Map(), map => map
|
||||||
|
.set('isLoading', false)
|
||||||
|
.set('next', next)
|
||||||
|
.update('items', list => list.concat(moreIds)));
|
||||||
|
};
|
||||||
|
|
||||||
const updateTimeline = (state, timeline, status, references) => {
|
const updateTimeline = (state, timeline, status, references) => {
|
||||||
const top = state.getIn([timeline, 'top']);
|
const top = state.getIn([timeline, 'top']);
|
||||||
|
|
||||||
|
@ -205,6 +240,7 @@ const deleteStatus = (state, id, accountId, references, reblogOf) => {
|
||||||
|
|
||||||
// Remove references from account timelines
|
// Remove references from account timelines
|
||||||
state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
|
state = state.updateIn(['accounts_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
|
||||||
|
state = state.updateIn(['accounts_media_timelines', accountId, 'items'], Immutable.List([]), list => list.filterNot(item => item === id));
|
||||||
|
|
||||||
// Remove references from context
|
// Remove references from context
|
||||||
state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
|
state.getIn(['descendants', id], Immutable.List()).forEach(descendantId => {
|
||||||
|
@ -302,6 +338,16 @@ export default function timelines(state = initialState, action) {
|
||||||
return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
|
return normalizeAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.replace);
|
||||||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
||||||
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
|
return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_FETCH_REQUEST:
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_EXPAND_REQUEST:
|
||||||
|
return state.updateIn(['accounts_media_timelines', action.id], Immutable.Map(), map => map.set('isLoading', true));
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_FETCH_FAIL:
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_EXPAND_FAIL:
|
||||||
|
return state.updateIn(['accounts_media_timelines', action.id], Immutable.Map(), map => map.set('isLoading', false));
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS:
|
||||||
|
return normalizeAccountMediaTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
|
||||||
|
case ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS:
|
||||||
|
return appendNormalizedAccountMediaTimeline(state, action.id, Immutable.fromJS(action.statuses), action.next);
|
||||||
case ACCOUNT_BLOCK_SUCCESS:
|
case ACCOUNT_BLOCK_SUCCESS:
|
||||||
case ACCOUNT_MUTE_SUCCESS:
|
case ACCOUNT_MUTE_SUCCESS:
|
||||||
return filterTimelines(state, action.relationship, action.statuses);
|
return filterTimelines(state, action.relationship, action.statuses);
|
||||||
|
|
|
@ -74,3 +74,17 @@ export const makeGetNotification = () => {
|
||||||
return base.set('account', account);
|
return base.set('account', account);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAccountGallery = createSelector([
|
||||||
|
(state, id) => state.getIn(['timelines', 'accounts_media_timelines', id, 'items'], Immutable.List()),
|
||||||
|
state => state.get('statuses'),
|
||||||
|
], (statusIds, statuses) => {
|
||||||
|
let medias = Immutable.List();
|
||||||
|
|
||||||
|
statusIds.forEach(statusId => {
|
||||||
|
const status = statuses.get(statusId);
|
||||||
|
medias = medias.concat(status.get('media_attachments').map(media => media.set('status', status)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return medias;
|
||||||
|
});
|
||||||
|
|
|
@ -3427,3 +3427,55 @@ button.icon-button.active i.fa-retweet {
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
}
|
}
|
||||||
/* End Video Player */
|
/* End Video Player */
|
||||||
|
|
||||||
|
.account-gallery__container {
|
||||||
|
margin: -2px;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-gallery__item {
|
||||||
|
float: left;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
margin: 2px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: $base-overlay-background;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-section-headline {
|
||||||
|
color: lighten($ui-base-color, 26%);
|
||||||
|
background: lighten($ui-base-color, 2%);
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
||||||
|
padding: 15px 10px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
position: relative;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 18px;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-style: solid;
|
||||||
|
border-width: 0 10px 10px;
|
||||||
|
border-color: transparent transparent lighten($ui-base-color, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
bottom: -1px;
|
||||||
|
border-color: transparent transparent $ui-base-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue