From f00af46d706f22e1e24d229e3c3f8f959ffaf22e Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 16 Mar 2018 19:54:00 +0100 Subject: [PATCH 1/3] [Glitch] Add "Toots/Toots with replies/Media" tab below profile header Port 51310125051a75ef7af4e8ffc8b6532c151e96b6 to glitch --- .../flavours/glitch/actions/timelines.js | 4 +- .../features/account/components/action_bar.js | 4 +- .../account_gallery/components/media_item.js | 16 ++-- .../glitch/features/account_gallery/index.js | 5 -- .../account_timeline/components/header.js | 8 ++ .../glitch/features/account_timeline/index.js | 23 +++-- .../flavours/glitch/features/ui/index.js | 1 + .../glitch/styles/components/accounts.scss | 90 ++++++++++++------- .../glitch/styles/components/media.scss | 1 + .../glitch/util/react_router_helpers.js | 9 +- 10 files changed, 104 insertions(+), 57 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 3a9d64084c..97cb02efd9 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -121,7 +121,7 @@ export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct'); -export const refreshAccountTimeline = accountId => refreshTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); +export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); @@ -163,7 +163,7 @@ export const expandHomeTimeline = () => expandTimeline('home', '/api/v1/ export const expandPublicTimeline = () => expandTimeline('public', '/api/v1/timelines/public'); export const expandCommunityTimeline = () => expandTimeline('community', '/api/v1/timelines/public', { local: true }); export const expandDirectTimeline = () => expandTimeline('direct', '/api/v1/timelines/direct'); -export const expandAccountTimeline = accountId => expandTimeline(`account:${accountId}`, `/api/v1/accounts/${accountId}/statuses`); +export const expandAccountTimeline = (accountId, withReplies) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }) export const expandAccountMediaTimeline = accountId => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); export const expandHashtagTimeline = hashtag => expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); export const expandListTimeline = id => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); diff --git a/app/javascript/flavours/glitch/features/account/components/action_bar.js b/app/javascript/flavours/glitch/features/account/components/action_bar.js index df8cb37337..fb90722f3d 100644 --- a/app/javascript/flavours/glitch/features/account/components/action_bar.js +++ b/app/javascript/flavours/glitch/features/account/components/action_bar.js @@ -53,11 +53,11 @@ export default class ActionBar extends React.PureComponent { let extraInfo = ''; menu.push({ text: intl.formatMessage(messages.mention, { name: account.get('username') }), action: this.props.onMention }); + if ('share' in navigator) { menu.push({ text: intl.formatMessage(messages.share, { name: account.get('username') }), action: this.handleShare }); } - menu.push(null); - menu.push({ text: intl.formatMessage(messages.media), to: `/accounts/${account.get('id')}/media` }); + menu.push(null); if (account.get('id') === me) { diff --git a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js index e52d3b0bb0..c2cf48d7bb 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js +++ b/app/javascript/flavours/glitch/features/account_gallery/components/media_item.js @@ -12,24 +12,26 @@ export default class MediaItem extends ImmutablePureComponent { render () { const { media } = this.props; const status = media.get('status'); + const focusX = media.getIn(['meta', 'focus', 'x']); + const focusY = media.getIn(['meta', 'focus', 'y']); + const x = ((focusX / 2) + .5) * 100; + const y = ((focusY / -2) + .5) * 100; + const style = {}; - let content, style; + let content; if (media.get('type') === 'gifv') { content = GIF; } if (!status.get('sensitive')) { - style = { backgroundImage: `url(${media.get('preview_url')})` }; + style.backgroundImage = `url(${media.get('preview_url')})`; + style.backgroundPosition = `${x}% ${y}%`; } return (
- + {content}
diff --git a/app/javascript/flavours/glitch/features/account_gallery/index.js b/app/javascript/flavours/glitch/features/account_gallery/index.js index df66b3b21b..63ff98debe 100644 --- a/app/javascript/flavours/glitch/features/account_gallery/index.js +++ b/app/javascript/flavours/glitch/features/account_gallery/index.js @@ -11,7 +11,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { getAccountGallery } from 'flavours/glitch/selectors'; import MediaItem from './components/media_item'; import HeaderContainer from 'flavours/glitch/features/account_timeline/containers/header_container'; -import { FormattedMessage } from 'react-intl'; import { ScrollContainer } from 'react-router-scroll-4'; import LoadMore from 'flavours/glitch/components/load_more'; @@ -89,10 +88,6 @@ export default class AccountGallery extends ImmutablePureComponent {
-
- -
-
{medias.map(media => ( + +
+ + + +
); } diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index 75dba50490..a391a1c804 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -12,11 +12,15 @@ import ColumnBackButton from '../../components/column_back_button'; import { List as ImmutableList } from 'immutable'; import ImmutablePureComponent from 'react-immutable-pure-component'; -const mapStateToProps = (state, props) => ({ - statusIds: state.getIn(['timelines', `account:${props.params.accountId}`, 'items'], ImmutableList()), - isLoading: state.getIn(['timelines', `account:${props.params.accountId}`, 'isLoading']), - hasMore: !!state.getIn(['timelines', `account:${props.params.accountId}`, 'next']), -}); +const mapStateToProps = (state, { params: { accountId }, withReplies = false }) => { + const path = withReplies ? `${accountId}:with_replies` : accountId; + + return { + statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), + isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), + hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), + }; +}; @connect(mapStateToProps) export default class AccountTimeline extends ImmutablePureComponent { @@ -27,23 +31,24 @@ export default class AccountTimeline extends ImmutablePureComponent { statusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, + withReplies: PropTypes.bool, }; componentWillMount () { this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(refreshAccountTimeline(this.props.params.accountId)); + this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies)); } componentWillReceiveProps (nextProps) { - if (nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) { + if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); - this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId)); + this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); } } handleScrollToBottom = () => { if (!this.props.isLoading && this.props.hasMore) { - this.props.dispatch(expandAccountTimeline(this.props.params.accountId)); + this.props.dispatch(expandAccountTimeline(this.props.params.accountId, this.props.withReplies)); } } diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index fae705deb7..0b031a7f0e 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -431,6 +431,7 @@ export default class UI extends React.Component { + diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 2bc894d253..81adf7c312 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -380,64 +380,94 @@ } .account-gallery__container { - margin: -2px; - padding: 4px; display: flex; + justify-content: center; flex-wrap: wrap; + padding: 2px; } .account-gallery__item { - flex: 1 1 auto; - width: calc(100% / 3 - 4px); - height: 95px; - margin: 2px; + flex-grow: 1; + width: 50%; + overflow: hidden; + position: relative; + + &::before { + content: ""; + display: block; + padding-top: 100%; + } a { display: block; - width: 100%; - height: 100%; + width: calc(100% - 4px); + height: calc(100% - 4px); + margin: 2px; + top: 0; + left: 0; background-color: $base-overlay-background; background-size: cover; background-position: center; - position: relative; + position: absolute; color: inherit; text-decoration: none; + border-radius: 4px; &:hover, &:active, &:focus { outline: 0; + + &::before { + content: ""; + display: block; + width: 100%; + height: 100%; + background: rgba($base-overlay-background, 0.3); + border-radius: 4px; + } } } } -.account-section-headline { - color: $ui-base-lighter-color; +.account__section-headline { 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; + display: flex; - &::before, - &::after { + a { 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%); - } + color: $ui-base-lighter-color; + padding: 15px 10px; + font-size: 14px; + font-weight: 500; + text-decoration: none; + position: relative; - &::after { - bottom: -1px; - border-color: transparent transparent $ui-base-color; + &.active { + color: $ui-highlight-color; + + &::before, + &::after { + display: block; + content: ""; + position: absolute; + bottom: 0; + left: 50%; + width: 0; + height: 0; + transform: translateX(-50%); + 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; + } + } } } diff --git a/app/javascript/flavours/glitch/styles/components/media.scss b/app/javascript/flavours/glitch/styles/components/media.scss index 4dd7482277..d7407cdaf4 100644 --- a/app/javascript/flavours/glitch/styles/components/media.scss +++ b/app/javascript/flavours/glitch/styles/components/media.scss @@ -102,6 +102,7 @@ &.standalone { .media-gallery__item-gifv-thumbnail { transform: none; + top: 0; } } } diff --git a/app/javascript/flavours/glitch/util/react_router_helpers.js b/app/javascript/flavours/glitch/util/react_router_helpers.js index 1dba5e9bb0..e36c512f34 100644 --- a/app/javascript/flavours/glitch/util/react_router_helpers.js +++ b/app/javascript/flavours/glitch/util/react_router_helpers.js @@ -35,14 +35,19 @@ export class WrappedRoute extends React.Component { component: PropTypes.func.isRequired, content: PropTypes.node, multiColumn: PropTypes.bool, + componentParams: PropTypes.object, } + static defaultProps = { + componentParams: {}, + }; + renderComponent = ({ match }) => { - const { component, content, multiColumn } = this.props; + const { component, content, multiColumn, componentParams } = this.props; return ( - {Component => {content}} + {Component => {content}} ); } From cd73af3bd08f070ebb88e9e1afe39bf414683496 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 16 Mar 2018 20:48:22 +0100 Subject: [PATCH 2/3] [Glitch] Improve style of web UI account tabs Port of 38b9af76a2365b2099dd2d6a77225a4ace8c290f to glitch --- .../glitch/styles/components/accounts.scss | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/app/javascript/flavours/glitch/styles/components/accounts.scss b/app/javascript/flavours/glitch/styles/components/accounts.scss index 81adf7c312..a86120e085 100644 --- a/app/javascript/flavours/glitch/styles/components/accounts.scss +++ b/app/javascript/flavours/glitch/styles/components/accounts.scss @@ -431,22 +431,24 @@ } .account__section-headline { - background: lighten($ui-base-color, 2%); - border-bottom: 1px solid lighten($ui-base-color, 4%); + background: darken($ui-base-color, 4%); + border-bottom: 1px solid lighten($ui-base-color, 8%); cursor: default; display: flex; a { display: block; - color: $ui-base-lighter-color; - padding: 15px 10px; + flex: 1 1 auto; + color: $ui-primary-color; + padding: 15px 0; font-size: 14px; font-weight: 500; + text-align: center; text-decoration: none; position: relative; &.active { - color: $ui-highlight-color; + color: $ui-secondary-color; &::before, &::after { @@ -460,7 +462,7 @@ transform: translateX(-50%); border-style: solid; border-width: 0 10px 10px; - border-color: transparent transparent lighten($ui-base-color, 4%); + border-color: transparent transparent lighten($ui-base-color, 8%); } &::after { From 6f0e50f9a0f8decc64acc92e02faace8e29153ad Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Fri, 16 Mar 2018 20:29:42 +0100 Subject: [PATCH 3/3] [Glitch] Federate pinned statuses over ActivityPub Port 9110db41c53a2f3f22affc23b364362133997d3e to glitch --- .../flavours/glitch/actions/timelines.js | 17 +++++++++-------- .../flavours/glitch/components/status_list.js | 19 ++++++++++++++++--- .../glitch/components/status_prepend.js | 10 +++++++--- .../glitch/containers/status_container.js | 5 ++++- .../account_timeline/components/header.js | 15 +++++++++------ .../glitch/features/account_timeline/index.js | 15 +++++++++++---- .../glitch/features/followers/index.js | 2 +- .../glitch/features/following/index.js | 2 +- 8 files changed, 58 insertions(+), 27 deletions(-) diff --git a/app/javascript/flavours/glitch/actions/timelines.js b/app/javascript/flavours/glitch/actions/timelines.js index 97cb02efd9..d99c6d98bc 100644 --- a/app/javascript/flavours/glitch/actions/timelines.js +++ b/app/javascript/flavours/glitch/actions/timelines.js @@ -117,14 +117,15 @@ export function refreshTimeline(timelineId, path, params = {}) { }; }; -export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home'); -export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); -export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); -export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct'); -export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); -export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); -export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); -export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); +export const refreshHomeTimeline = () => refreshTimeline('home', '/api/v1/timelines/home'); +export const refreshPublicTimeline = () => refreshTimeline('public', '/api/v1/timelines/public'); +export const refreshCommunityTimeline = () => refreshTimeline('community', '/api/v1/timelines/public', { local: true }); +export const refreshDirectTimeline = () => refreshTimeline('direct', '/api/v1/timelines/direct'); +export const refreshAccountTimeline = (accountId, withReplies) => refreshTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { exclude_replies: !withReplies }); +export const refreshAccountFeaturedTimeline = accountId => refreshTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true }); +export const refreshAccountMediaTimeline = accountId => refreshTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { only_media: true }); +export const refreshHashtagTimeline = hashtag => refreshTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`); +export const refreshListTimeline = id => refreshTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`); export function refreshTimelineFail(timeline, error, skipLoading) { return { diff --git a/app/javascript/flavours/glitch/components/status_list.js b/app/javascript/flavours/glitch/components/status_list.js index f253f0fdc1..ea40463da8 100644 --- a/app/javascript/flavours/glitch/components/status_list.js +++ b/app/javascript/flavours/glitch/components/status_list.js @@ -11,6 +11,7 @@ export default class StatusList extends ImmutablePureComponent { static propTypes = { scrollKey: PropTypes.string.isRequired, statusIds: ImmutablePropTypes.list.isRequired, + featuredStatusIds: ImmutablePropTypes.list, onScrollToBottom: PropTypes.func, onScrollToTop: PropTypes.func, onScroll: PropTypes.func, @@ -50,7 +51,7 @@ export default class StatusList extends ImmutablePureComponent { } render () { - const { statusIds, ...other } = this.props; + const { statusIds, featuredStatusIds, ...other } = this.props; const { isLoading, isPartial } = other; if (isPartial) { @@ -68,8 +69,8 @@ export default class StatusList extends ImmutablePureComponent { ); } - const scrollableContent = (isLoading || statusIds.size > 0) ? ( - statusIds.map((statusId) => ( + let scrollableContent = (isLoading || statusIds.size > 0) ? ( + statusIds.map(statusId => ( ( + + )).concat(scrollableContent); + } + return ( {scrollableContent} diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index bd2559e46d..f4ef83135f 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -34,6 +34,10 @@ export default class StatusPrepend extends React.PureComponent { ); switch (type) { + case 'featured': + return ( + + ); case 'reblogged_by': return ( -
+
); } diff --git a/app/javascript/flavours/glitch/features/account_timeline/index.js b/app/javascript/flavours/glitch/features/account_timeline/index.js index a391a1c804..fbb16dff9b 100644 --- a/app/javascript/flavours/glitch/features/account_timeline/index.js +++ b/app/javascript/flavours/glitch/features/account_timeline/index.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import ImmutablePropTypes from 'react-immutable-proptypes'; import PropTypes from 'prop-types'; import { fetchAccount } from 'flavours/glitch/actions/accounts'; -import { refreshAccountTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; +import { refreshAccountTimeline, refreshAccountFeaturedTimeline, expandAccountTimeline } from 'flavours/glitch/actions/timelines'; import StatusList from '../../components/status_list'; import LoadingIndicator from '../../components/loading_indicator'; import Column from '../ui/components/column'; @@ -17,6 +17,7 @@ const mapStateToProps = (state, { params: { accountId }, withReplies = false }) return { statusIds: state.getIn(['timelines', `account:${path}`, 'items'], ImmutableList()), + featuredStatusIds: withReplies ? ImmutableList() : state.getIn(['timelines', `account:${accountId}:pinned`, 'items'], ImmutableList()), isLoading: state.getIn(['timelines', `account:${path}`, 'isLoading']), hasMore: !!state.getIn(['timelines', `account:${path}`, 'next']), }; @@ -29,19 +30,24 @@ export default class AccountTimeline extends ImmutablePureComponent { params: PropTypes.object.isRequired, dispatch: PropTypes.func.isRequired, statusIds: ImmutablePropTypes.list, + featuredStatusIds: ImmutablePropTypes.list, isLoading: PropTypes.bool, hasMore: PropTypes.bool, withReplies: PropTypes.bool, }; componentWillMount () { - this.props.dispatch(fetchAccount(this.props.params.accountId)); - this.props.dispatch(refreshAccountTimeline(this.props.params.accountId, this.props.withReplies)); + const { params: { accountId }, withReplies } = this.props; + + this.props.dispatch(fetchAccount(accountId)); + this.props.dispatch(refreshAccountFeaturedTimeline(accountId)); + this.props.dispatch(refreshAccountTimeline(accountId, withReplies)); } componentWillReceiveProps (nextProps) { if ((nextProps.params.accountId !== this.props.params.accountId && nextProps.params.accountId) || nextProps.withReplies !== this.props.withReplies) { this.props.dispatch(fetchAccount(nextProps.params.accountId)); + this.props.dispatch(refreshAccountFeaturedTimeline(nextProps.params.accountId)); this.props.dispatch(refreshAccountTimeline(nextProps.params.accountId, nextProps.params.withReplies)); } } @@ -53,7 +59,7 @@ export default class AccountTimeline extends ImmutablePureComponent { } render () { - const { statusIds, isLoading, hasMore } = this.props; + const { statusIds, featuredStatusIds, isLoading, hasMore } = this.props; if (!statusIds && isLoading) { return ( @@ -71,6 +77,7 @@ export default class AccountTimeline extends ImmutablePureComponent { prepend={} scrollKey='account_timeline' statusIds={statusIds} + featuredStatusIds={featuredStatusIds} isLoading={isLoading} hasMore={hasMore} onScrollToBottom={this.handleScrollToBottom} diff --git a/app/javascript/flavours/glitch/features/followers/index.js b/app/javascript/flavours/glitch/features/followers/index.js index f0ef29ff68..c42e0386c9 100644 --- a/app/javascript/flavours/glitch/features/followers/index.js +++ b/app/javascript/flavours/glitch/features/followers/index.js @@ -80,7 +80,7 @@ export default class Followers extends ImmutablePureComponent {
- + {accountIds.map(id => )} {loadMore}
diff --git a/app/javascript/flavours/glitch/features/following/index.js b/app/javascript/flavours/glitch/features/following/index.js index f30f7b0d98..c05742d4f3 100644 --- a/app/javascript/flavours/glitch/features/following/index.js +++ b/app/javascript/flavours/glitch/features/following/index.js @@ -80,7 +80,7 @@ export default class Following extends ImmutablePureComponent {
- + {accountIds.map(id => )} {loadMore}