Add account media gallery view to web UI (#3120)
* Add account media gallery view to web UI * Link media view from account dropdown * Adjust link
This commit is contained in:
parent
b369fc2de4
commit
de475cf8d3
|
@ -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_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_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS';
|
||||
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 newestId = ids.size > 0 ? ids.first() : null;
|
||||
|
||||
let params = '';
|
||||
let params = {};
|
||||
let skipLoading = false;
|
||||
|
||||
if (newestId !== null && !replace) {
|
||||
params = `?since_id=${newestId}`;
|
||||
params.since_id = newestId;
|
||||
skipLoading = true;
|
||||
}
|
||||
|
||||
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));
|
||||
}).catch(error => {
|
||||
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) {
|
||||
return (dispatch, getState) => {
|
||||
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) {
|
||||
return {
|
||||
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) {
|
||||
return {
|
||||
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) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(blockAccountRequest(id));
|
||||
|
|
|
@ -4,6 +4,10 @@ import PropTypes from 'prop-types';
|
|||
|
||||
class DropdownMenu extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
items: PropTypes.array.isRequired,
|
||||
|
@ -26,13 +30,17 @@ class DropdownMenu extends React.PureComponent {
|
|||
|
||||
handleClick = (e) => {
|
||||
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') {
|
||||
e.preventDefault();
|
||||
action();
|
||||
this.dropdown.hide();
|
||||
} else if (to) {
|
||||
this.context.router.push(to);
|
||||
}
|
||||
|
||||
this.dropdown.hide();
|
||||
}
|
||||
|
||||
renderItem = (item, i) => {
|
||||
|
|
|
@ -26,6 +26,7 @@ import GettingStarted from '../features/getting_started';
|
|||
import PublicTimeline from '../features/public_timeline';
|
||||
import CommunityTimeline from '../features/community_timeline';
|
||||
import AccountTimeline from '../features/account_timeline';
|
||||
import AccountGallery from '../features/account_gallery';
|
||||
import HomeTimeline from '../features/home_timeline';
|
||||
import Compose from '../features/compose';
|
||||
import Followers from '../features/followers';
|
||||
|
@ -204,6 +205,7 @@ class Mastodon extends React.PureComponent {
|
|||
<Route path='accounts/:accountId' component={AccountTimeline} />
|
||||
<Route path='accounts/:accountId/followers' component={Followers} />
|
||||
<Route path='accounts/:accountId/following' component={Following} />
|
||||
<Route path='accounts/:accountId/media' component={AccountGallery} />
|
||||
|
||||
<Route path='follow_requests' component={FollowRequests} />
|
||||
<Route path='blocks' component={Blocks} />
|
||||
|
|
|
@ -15,6 +15,7 @@ const messages = defineMessages({
|
|||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
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.' },
|
||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {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(null);
|
||||
menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` });
|
||||
menu.push(null);
|
||||
|
||||
if (account.get('id') === me) {
|
||||
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;
|
99
app/javascript/mastodon/features/account_gallery/index.js
Normal file
99
app/javascript/mastodon/features/account_gallery/index.js
Normal file
|
@ -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
|
||||
};
|
||||
|
||||
constructor (props, context) {
|
||||
super(props, context);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchBlocks());
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) {
|
||||
|
|
|
@ -26,7 +26,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
this.props.dispatch(fetchFavourites(Number(this.props.params.statusId)));
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
componentWillReceiveProps (nextProps) {
|
||||
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
|
||||
this.props.dispatch(fetchFavourites(Number(nextProps.params.statusId)));
|
||||
}
|
||||
|
|
|
@ -21,16 +21,11 @@ const mapStateToProps = state => ({
|
|||
|
||||
class Mutes extends ImmutablePureComponent {
|
||||
|
||||
constructor (props, context) {
|
||||
super(props, context);
|
||||
this.handleScroll = this.handleScroll.bind(this);
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchMutes());
|
||||
}
|
||||
|
||||
handleScroll (e) {
|
||||
handleScroll = (e) => {
|
||||
const { scrollTop, scrollHeight, clientHeight } = e.target;
|
||||
|
||||
if (scrollTop === scrollHeight - clientHeight) {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"account.followers": "フォロワー",
|
||||
"account.follows": "フォロー",
|
||||
"account.follows_you": "フォローされています",
|
||||
"account.media": "Media",
|
||||
"account.media": "メディア",
|
||||
"account.mention": "返信",
|
||||
"account.mute": "ミュート",
|
||||
"account.posts": "投稿",
|
||||
|
|
|
@ -23,6 +23,8 @@ import {
|
|||
import {
|
||||
ACCOUNT_TIMELINE_FETCH_SUCCESS,
|
||||
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||
ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS,
|
||||
ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS,
|
||||
ACCOUNT_BLOCK_SUCCESS
|
||||
} from '../actions/accounts';
|
||||
import {
|
||||
|
@ -113,6 +115,8 @@ export default function statuses(state = initialState, action) {
|
|||
case TIMELINE_EXPAND_SUCCESS:
|
||||
case ACCOUNT_TIMELINE_FETCH_SUCCESS:
|
||||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
||||
case ACCOUNT_MEDIA_TIMELINE_FETCH_SUCCESS:
|
||||
case ACCOUNT_MEDIA_TIMELINE_EXPAND_SUCCESS:
|
||||
case CONTEXT_FETCH_SUCCESS:
|
||||
case NOTIFICATIONS_REFRESH_SUCCESS:
|
||||
case NOTIFICATIONS_EXPAND_SUCCESS:
|
||||
|
|
|
@ -24,6 +24,12 @@ import {
|
|||
ACCOUNT_TIMELINE_EXPAND_REQUEST,
|
||||
ACCOUNT_TIMELINE_EXPAND_SUCCESS,
|
||||
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_MUTE_SUCCESS
|
||||
} from '../actions/accounts';
|
||||
|
@ -79,6 +85,7 @@ const initialState = Immutable.Map({
|
|||
}),
|
||||
|
||||
accounts_timelines: Immutable.Map(),
|
||||
accounts_media_timelines: Immutable.Map(),
|
||||
ancestors: 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))));
|
||||
};
|
||||
|
||||
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) => {
|
||||
let moreIds = Immutable.List([]);
|
||||
|
||||
|
@ -162,6 +183,20 @@ const appendNormalizedAccountTimeline = (state, accountId, statuses, next) => {
|
|||
.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 top = state.getIn([timeline, 'top']);
|
||||
|
||||
|
@ -205,6 +240,7 @@ const deleteStatus = (state, id, accountId, references, reblogOf) => {
|
|||
|
||||
// Remove references from account timelines
|
||||
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
|
||||
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);
|
||||
case ACCOUNT_TIMELINE_EXPAND_SUCCESS:
|
||||
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_MUTE_SUCCESS:
|
||||
return filterTimelines(state, action.relationship, action.statuses);
|
||||
|
|
|
@ -74,3 +74,17 @@ export const makeGetNotification = () => {
|
|||
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%);
|
||||
}
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue