This commit is contained in:
mgabdev 2020-02-29 10:42:47 -05:00
parent 3ca4ffcc6b
commit c6aa4e08a1
190 changed files with 1156 additions and 1042 deletions

View File

@ -2,13 +2,13 @@ import api, { getLinks } from '../api';
import { importFetchedStatuses } from './importer'; import { importFetchedStatuses } from './importer';
import { me } from '../initial_state'; import { me } from '../initial_state';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; export const FAVORITED_STATUSES_FETCH_REQUEST = 'FAVORITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; export const FAVORITED_STATUSES_FETCH_SUCCESS = 'FAVORITED_STATUSES_FETCH_SUCCESS';
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; export const FAVORITED_STATUSES_FETCH_FAIL = 'FAVORITED_STATUSES_FETCH_FAIL';
export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; export const FAVORITED_STATUSES_EXPAND_REQUEST = 'FAVORITED_STATUSES_EXPAND_REQUEST';
export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; export const FAVORITED_STATUSES_EXPAND_SUCCESS = 'FAVORITED_STATUSES_EXPAND_SUCCESS';
export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; export const FAVORITED_STATUSES_EXPAND_FAIL = 'FAVORITED_STATUSES_EXPAND_FAIL';
export function fetchFavoritedStatuses() { export function fetchFavoritedStatuses() {
return (dispatch, getState) => { return (dispatch, getState) => {
@ -32,14 +32,14 @@ export function fetchFavoritedStatuses() {
export function fetchFavoritedStatusesRequest() { export function fetchFavoritedStatusesRequest() {
return { return {
type: FAVOURITED_STATUSES_FETCH_REQUEST, type: FAVORITED_STATUSES_FETCH_REQUEST,
skipLoading: true, skipLoading: true,
}; };
}; };
export function fetchFavoritedStatusesSuccess(statuses, next) { export function fetchFavoritedStatusesSuccess(statuses, next) {
return { return {
type: FAVOURITED_STATUSES_FETCH_SUCCESS, type: FAVORITED_STATUSES_FETCH_SUCCESS,
statuses, statuses,
next, next,
skipLoading: true, skipLoading: true,
@ -48,7 +48,7 @@ export function fetchFavoritedStatusesSuccess(statuses, next) {
export function fetchFavoritedStatusesFail(error) { export function fetchFavoritedStatusesFail(error) {
return { return {
type: FAVOURITED_STATUSES_FETCH_FAIL, type: FAVORITED_STATUSES_FETCH_FAIL,
error, error,
skipLoading: true, skipLoading: true,
}; };
@ -78,13 +78,13 @@ export function expandFavoritedStatuses() {
export function expandFavoritedStatusesRequest() { export function expandFavoritedStatusesRequest() {
return { return {
type: FAVOURITED_STATUSES_EXPAND_REQUEST, type: FAVORITED_STATUSES_EXPAND_REQUEST,
}; };
}; };
export function expandFavoritedStatusesSuccess(statuses, next) { export function expandFavoritedStatusesSuccess(statuses, next) {
return { return {
type: FAVOURITED_STATUSES_EXPAND_SUCCESS, type: FAVORITED_STATUSES_EXPAND_SUCCESS,
statuses, statuses,
next, next,
}; };
@ -92,7 +92,7 @@ export function expandFavoritedStatusesSuccess(statuses, next) {
export function expandFavoritedStatusesFail(error) { export function expandFavoritedStatusesFail(error) {
return { return {
type: FAVOURITED_STATUSES_EXPAND_FAIL, type: FAVORITED_STATUSES_EXPAND_FAIL,
error, error,
}; };
}; };

View File

@ -0,0 +1,26 @@
const ErrorIcon = ({
className = '',
width = '32px',
height = '32px',
viewBox = '0 0 64 64',
title = 'Error',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d="M 54.667969 9.371094 C 42.179688 -3.121094 21.855469 -3.121094 9.367188 9.367188 C -3.125 21.855469 -3.121094 42.179688 9.367188 54.667969 C 21.855469 67.160156 42.179688 67.160156 54.667969 54.667969 C 67.15625 42.179688 67.15625 21.855469 54.667969 9.371094 Z M 51.175781 51.175781 C 40.613281 61.738281 23.425781 61.738281 12.863281 51.175781 C 2.296875 40.613281 2.296875 23.421875 12.863281 12.859375 C 23.425781 2.296875 40.609375 2.296875 51.175781 12.863281 C 61.738281 23.425781 61.738281 40.613281 51.175781 51.175781 Z M 46.5 44.679688 C 46.898438 45.597656 46.476562 46.664062 45.558594 47.058594 C 44.640625 47.460938 43.574219 47.035156 43.175781 46.117188 C 41.429688 42.078125 37.300781 39.46875 32.65625 39.46875 C 27.90625 39.46875 23.753906 42.078125 22.078125 46.113281 C 21.789062 46.808594 21.113281 47.230469 20.40625 47.230469 C 20.175781 47.230469 19.9375 47.183594 19.710938 47.089844 C 18.789062 46.707031 18.351562 45.648438 18.734375 44.722656 C 20.972656 39.332031 26.441406 35.847656 32.65625 35.847656 C 38.746094 35.847656 44.179688 39.3125 46.5 44.679688 Z M 20.070312 23.347656 C 20.070312 21.28125 21.746094 19.605469 23.8125 19.605469 C 25.878906 19.605469 27.558594 21.28125 27.558594 23.347656 C 27.558594 25.417969 25.878906 27.09375 23.8125 27.09375 C 21.746094 27.09375 20.070312 25.417969 20.070312 23.347656 Z M 37.046875 23.347656 C 37.046875 21.28125 38.722656 19.605469 40.789062 19.605469 C 42.859375 19.605469 44.535156 21.28125 44.535156 23.347656 C 44.535156 25.417969 42.859375 27.09375 40.789062 27.09375 C 38.722656 27.09375 37.046875 25.417969 37.046875 23.347656 Z M 37.046875 23.347656" />
</g>
</svg>
)
export default ErrorIcon

View File

@ -9,15 +9,20 @@ import CloseIcon from './close_icon'
import CommentIcon from './comment_icon' import CommentIcon from './comment_icon'
import DissenterIcon from './dissenter_icon' import DissenterIcon from './dissenter_icon'
import EllipsisIcon from './ellipsis_icon' import EllipsisIcon from './ellipsis_icon'
import ErrorIcon from './error_icon'
import GlobeIcon from './globe_icon' import GlobeIcon from './globe_icon'
import GroupIcon from './group_icon' import GroupIcon from './group_icon'
import HomeIcon from './home_icon' import HomeIcon from './home_icon'
import LikeIcon from './like_icon' import LikeIcon from './like_icon'
import LinkIcon from './link_icon'
import ListIcon from './list_icon' import ListIcon from './list_icon'
import LoadingIcon from './loading_icon' import LoadingIcon from './loading_icon'
import MediaIcon from './media_icon' import MediaIcon from './media_icon'
import MissingIcon from './missing_icon'
import MoreIcon from './more_icon' import MoreIcon from './more_icon'
import NotificationsIcon from './notifications_icon' import NotificationsIcon from './notifications_icon'
import PinIcon from './pin_icon'
import PlayIcon from './play_icon'
import PollIcon from './poll_icon' import PollIcon from './poll_icon'
import RepostIcon from './repost_icon' import RepostIcon from './repost_icon'
import SearchIcon from './search_icon' import SearchIcon from './search_icon'
@ -40,15 +45,20 @@ export {
CommentIcon, CommentIcon,
DissenterIcon, DissenterIcon,
EllipsisIcon, EllipsisIcon,
ErrorIcon,
GlobeIcon, GlobeIcon,
GroupIcon, GroupIcon,
HomeIcon, HomeIcon,
LikeIcon, LikeIcon,
LinkIcon,
ListIcon, ListIcon,
LoadingIcon, LoadingIcon,
MediaIcon, MediaIcon,
MissingIcon,
MoreIcon, MoreIcon,
NotificationsIcon, NotificationsIcon,
PinIcon,
PlayIcon,
PollIcon, PollIcon,
RepostIcon, RepostIcon,
SearchIcon, SearchIcon,

View File

@ -0,0 +1,27 @@
const LinkIcon = ({
className = '',
width = '16px',
height = '16px',
viewBox = '0 0 64 64',
title = 'Link',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d="M 26.53125 48.78125 L 18.992188 56.320312 C 15.863281 59.449219 10.800781 59.449219 7.675781 56.324219 C 4.554688 53.199219 4.554688 48.132812 7.675781 45.011719 L 22.761719 29.925781 C 25.886719 26.800781 30.949219 26.800781 34.074219 29.925781 C 35.117188 30.964844 36.804688 30.964844 37.84375 29.925781 C 38.886719 28.882812 38.886719 27.195312 37.84375 26.152344 C 32.636719 20.945312 24.199219 20.945312 18.992188 26.152344 L 3.90625 41.238281 C -1.300781 46.445312 -1.300781 54.886719 3.90625 60.09375 C 9.113281 65.300781 17.554688 65.300781 22.761719 60.09375 L 30.304688 52.550781 C 31.34375 51.511719 31.34375 49.820312 30.304688 48.78125 C 29.261719 47.738281 27.574219 47.738281 26.53125 48.78125 Z M 26.53125 48.78125" />
<path d="M 60.09375 3.90625 C 54.886719 -1.300781 46.445312 -1.300781 41.238281 3.90625 L 32.1875 12.953125 C 31.148438 13.996094 31.148438 15.683594 32.1875 16.722656 C 33.230469 17.765625 34.917969 17.765625 35.960938 16.722656 L 45.007812 7.675781 C 48.132812 4.550781 53.199219 4.550781 56.324219 7.675781 C 59.445312 10.800781 59.445312 15.863281 56.324219 18.988281 L 39.730469 35.578125 C 36.605469 38.703125 31.542969 38.703125 28.417969 35.578125 C 27.378906 34.539062 25.691406 34.539062 24.648438 35.578125 C 23.605469 36.621094 23.605469 38.308594 24.648438 39.351562 C 29.855469 44.558594 38.296875 44.558594 43.503906 39.351562 L 60.09375 22.757812 C 65.300781 17.550781 65.300781 9.113281 60.09375 3.90625 Z M 60.09375 3.90625" />
</g>
</svg>
)
export default LinkIcon

View File

@ -0,0 +1,29 @@
const MissingIcon = ({
className = '',
width = '32px',
height = '32px',
viewBox = '0 0 84 84',
title = 'Missing',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d="M 30.21875 0 L 12.601562 17.621094 L 12.601562 84 L 71.398438 84 L 71.398438 0 Z M 29.398438 4.78125 L 29.398438 16.800781 L 17.378906 16.800781 Z M 68.601562 81.199219 L 15.398438 81.199219 L 15.398438 19.601562 L 32.199219 19.601562 L 32.199219 2.800781 L 68.601562 2.800781 Z M 68.601562 81.199219" />
<path d="M 34.757812 33.257812 L 30.800781 37.21875 L 26.839844 33.257812 L 24.859375 35.238281 L 28.820312 39.199219 L 24.859375 43.160156 L 26.839844 45.140625 L 30.800781 41.179688 L 34.757812 45.140625 L 36.738281 43.160156 L 32.78125 39.199219 L 36.738281 35.238281 Z M 34.757812 33.257812" />
<path d="M 47.839844 45.140625 L 51.800781 41.179688 L 55.757812 45.140625 L 57.738281 43.160156 L 53.78125 39.199219 L 57.738281 35.238281 L 55.757812 33.257812 L 51.800781 37.21875 L 47.839844 33.257812 L 45.859375 35.238281 L 49.820312 39.199219 L 45.859375 43.160156 Z M 47.839844 45.140625" />
<path d="M 29.808594 57.808594 L 31.789062 59.789062 L 33.089844 58.488281 C 38.015625 53.578125 45.984375 53.578125 50.910156 58.488281 L 52.210938 59.789062 L 54.191406 57.808594 L 52.890625 56.507812 C 46.871094 50.503906 37.128906 50.503906 31.109375 56.507812 Z M 29.808594 57.808594" />
</g>
</svg>
)
export default MissingIcon

View File

@ -0,0 +1,26 @@
const PinIcon = ({
className = '',
width = '16px',
height = '16px',
viewBox = '0 0 32 32',
title = 'Pin',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d="M 25.984375 19.042969 C 25.984375 19.914062 25.277344 20.621094 24.402344 20.621094 L 18.996094 20.621094 L 16.789062 31.398438 C 16.714844 31.765625 16.390625 32.03125 16.015625 32.03125 C 15.640625 32.03125 15.316406 31.765625 15.242188 31.398438 L 13.03125 20.621094 L 7.628906 20.621094 C 6.753906 20.621094 6.046875 19.914062 6.046875 19.042969 C 6.046875 16.636719 7.4375 14.488281 9.617188 13.050781 L 9.617188 8.335938 L 8.148438 8.335938 C 7.277344 8.335938 6.570312 7.628906 6.570312 6.753906 L 6.570312 1.582031 C 6.570312 0.707031 7.277344 0 8.148438 0 L 23.882812 0 C 24.757812 0 25.460938 0.707031 25.460938 1.582031 L 25.460938 6.753906 C 25.460938 7.628906 24.757812 8.335938 23.882812 8.335938 L 22.414062 8.335938 L 22.414062 13.050781 C 24.59375 14.488281 25.984375 16.636719 25.984375 19.042969 Z M 25.984375 19.042969" />
</g>
</svg>
)
export default PinIcon

View File

@ -0,0 +1,26 @@
const PlayIcon = ({
className = '',
width = '16px',
height = '16px',
viewBox = '0 0 64 64',
title = 'Play',
}) => (
<svg
className={className}
version='1.1'
xmlns='http://www.w3.org/2000/svg'
x='0px'
y='0px'
width={width}
height={height}
viewBox={viewBox}
xmlSpace='preserve'
aria-label={title}
>
<g>
<path d="M 52.492188 26.058594 L 16.941406 1.71875 C 15.300781 0.59375 13.644531 0 12.269531 0 C 9.613281 0 7.96875 2.132812 7.96875 5.703125 L 7.96875 58.304688 C 7.96875 61.871094 9.609375 64 12.261719 64 C 13.640625 64 15.265625 63.402344 16.914062 62.277344 L 52.476562 37.941406 C 54.765625 36.371094 56.03125 34.261719 56.03125 31.996094 C 56.03125 29.734375 54.78125 27.625 52.492188 26.058594 Z M 52.492188 26.058594" />
</g>
</svg>
)
export default PlayIcon

View File

@ -36,6 +36,7 @@ class Account extends ImmutablePureComponent {
actionIcon: PropTypes.string, actionIcon: PropTypes.string,
actionTitle: PropTypes.string, actionTitle: PropTypes.string,
onActionClick: PropTypes.func, onActionClick: PropTypes.func,
compact: PropTypes.bool,
} }
handleFollow = () => { handleFollow = () => {
@ -63,7 +64,15 @@ class Account extends ImmutablePureComponent {
} }
render() { render() {
const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props const {
account,
intl,
hidden,
onActionClick,
actionIcon,
actionTitle,
compact
} = this.props
if (!account) return null if (!account) return null
@ -76,6 +85,7 @@ class Account extends ImmutablePureComponent {
) )
} }
const avatarSize = compact ? 42 : 52
let buttons let buttons
if (onActionClick && actionIcon) { if (onActionClick && actionIcon) {
@ -109,6 +119,50 @@ class Account extends ImmutablePureComponent {
} }
} }
// : todo : clean up
if (compact) {
return (
<div className={[_s.default, _s.marginTop5PX, _s.marginBottom15PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
<NavLink
className={[_s.default, _s.noUnderline].join(' ')}
title={account.get('acct')}
to={`/${account.get('acct')}`}
>
<Avatar account={account} size={avatarSize} />
</NavLink>
<div className={[_s.default, _s.alignItemsStart, _s.paddingHorizontal10PX].join(' ')}>
<NavLink
className={[_s.default, _s.noUnderline].join(' ')}
title={account.get('acct')}
to={`/${account.get('acct')}`}
>
<DisplayName account={account} multiline />
</NavLink>
</div>
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
<Button
outline
narrow
color='brand'
backgroundColor='none'
className={_s.marginTop5PX}
>
<Text color='inherit'>
{intl.formatMessage(messages.follow)}
</Text>
</Button>
</div>
</div>
</div>
)
}
return ( return (
<div className={[_s.default, _s.marginTop5PX, _s.marginBottom15PX].join(' ')}> <div className={[_s.default, _s.marginTop5PX, _s.marginBottom15PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}> <div className={[_s.default, _s.flexRow].join(' ')}>
@ -118,7 +172,7 @@ class Account extends ImmutablePureComponent {
title={account.get('acct')} title={account.get('acct')}
to={`/${account.get('acct')}`} to={`/${account.get('acct')}`}
> >
<Avatar account={account} size={52} /> <Avatar account={account} size={avatarSize} />
</NavLink> </NavLink>
<div className={[_s.default, _s.alignItemsStart, _s.paddingHorizontal10PX].join(' ')}> <div className={[_s.default, _s.alignItemsStart, _s.paddingHorizontal10PX].join(' ')}>
@ -129,6 +183,7 @@ class Account extends ImmutablePureComponent {
> >
<DisplayName account={account} /> <DisplayName account={account} />
</NavLink> </NavLink>
<Button <Button
outline outline
narrow narrow

View File

@ -1 +1 @@
export { default } from './account'; export { default } from './account'

View File

@ -1 +1 @@
export { default } from './autosuggest_account'; export { default } from './autosuggest_account'

View File

@ -1 +1 @@
export { default } from './autosuggest_emoji'; export { default } from './autosuggest_emoji'

View File

@ -1 +1 @@
export { default } from './autosuggest_textbox'; export { default } from './autosuggest_textbox'

View File

@ -1 +1 @@
export { default } from './bundle_column_error'; export { default } from './bundle_column_error'

View File

@ -81,6 +81,10 @@ export default class Button extends PureComponent {
const theIcon = !!icon ? <Icon id={icon} width={iconWidth} height={iconWidth} className={iconClassName} /> : undefined const theIcon = !!icon ? <Icon id={icon} width={iconWidth} height={iconWidth} className={iconClassName} /> : undefined
if (backgroundColor === 'tertiary') {
console.log("className:", className)
}
// : todo : // : todo :
const classes = cx(className, { const classes = cx(className, {
default: 1, default: 1,
@ -93,6 +97,7 @@ export default class Button extends PureComponent {
backgroundColorBrand: backgroundColor === COLORS.brand, backgroundColorBrand: backgroundColor === COLORS.brand,
backgroundTransparent: backgroundColor === COLORS.none, backgroundTransparent: backgroundColor === COLORS.none,
backgroundSubtle2: backgroundColor === COLORS.tertiary, backgroundSubtle2: backgroundColor === COLORS.tertiary,
backgroundSubtle: backgroundColor === COLORS.secondary,
colorPrimary: color === COLORS.primary, colorPrimary: color === COLORS.primary,
colorSecondary: color === COLORS.secondary, colorSecondary: color === COLORS.secondary,
@ -131,9 +136,9 @@ export default class Button extends PureComponent {
) : children ) : children
const options = { const options = {
disabled,
className: classes, className: classes,
ref: this.setRef, ref: this.setRef,
disabled: disabled,
to: to || undefined, to: to || undefined,
href: href || undefined, href: href || undefined,
onClick: this.handleClick || undefined, onClick: this.handleClick || undefined,

View File

@ -1,5 +1,6 @@
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import Icon from './icon' import Icon from './icon'
import Text from './text'
const messages = defineMessages({ const messages = defineMessages({
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading..' }, loading: { id: 'loading_indicator.label', defaultMessage: 'Loading..' },
@ -30,12 +31,16 @@ class ColumnIndicator extends PureComponent {
return ( return (
<div className={[_s.default, _s.width100PC, _s.justifyContentCenter, _s.alignItemsCenter, _s.paddingVertical15PX].join(' ')}> <div className={[_s.default, _s.width100PC, _s.justifyContentCenter, _s.alignItemsCenter, _s.paddingVertical15PX].join(' ')}>
<Icon id={type} width='52px' height='52px' /> <Icon id={type} width='44px' height='44px' />
{ {
type !== 'loading' && type !== 'loading' &&
<span className={[_s.default, _s.marginTop10PX, _s.text, _s.displayFlex, _s.colorBrand, _s.fontWeightNormal, _s.fontSize14PX].join(' ')}> <Text
align='center'
size='medium'
className={_s.marginTop10PX}
>
{title} {title}
</span> </Text>
} }
</div> </div>
) )

View File

@ -1 +1 @@
export { default } from './column_inline_form'; export { default } from './column_inline_form'

View File

@ -31,12 +31,12 @@ class DisplayName extends ImmutablePureComponent {
} }
handleMouseEnter = debounce(() => { handleMouseEnter = debounce(() => {
console.log("SHOW - USER POPOVER") // console.log("SHOW - USER POPOVER")
this.props.openUserInfoPopover() // this.props.openUserInfoPopover()
}, 50, { leading: true }) }, 50, { leading: true })
handleMouseLeave = () => { handleMouseLeave = () => {
console.log("HIDE - USER POPOVER") // console.log("HIDE - USER POPOVER")
// this.props.closeUserInfoPopover() // this.props.closeUserInfoPopover()
} }

View File

@ -1,7 +1,25 @@
import classnames from 'classnames/bind'
const cx = classnames.bind(_s)
export default class Divider extends PureComponent { export default class Divider extends PureComponent {
static propTypes = {
small: PropTypes.bool
}
render() { render() {
const { small } = this.props
const classes = cx({
default: 1,
borderBottom1PX: 1,
borderColorSecondary2: 1,
width100PC: 1,
marginBottom15PX: !small,
marginVertical10PX: small,
})
return ( return (
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary2, _s.marginBottom15PX, _s.width100PC].join(' ')} /> <div className={classes} />
) )
} }
} }

View File

@ -1 +1 @@
export { default } from './domain'; export { default } from './domain'

View File

@ -1,10 +1,8 @@
import { FormattedMessage } from 'react-intl';
export default class ErrorBoundary extends PureComponent { export default class ErrorBoundary extends PureComponent {
static propTypes = { static propTypes = {
children: PropTypes.node, children: PropTypes.node,
}; }
state = { state = {
hasError: false, hasError: false,
@ -17,22 +15,23 @@ export default class ErrorBoundary extends PureComponent {
hasError: true, hasError: true,
stackTrace: error.stack, stackTrace: error.stack,
componentStack: info && info.componentStack, componentStack: info && info.componentStack,
}); })
} }
render() { render() {
const { hasError } = this.state; const { hasError } = this.state
if (!hasError) return this.props.children; if (!hasError) return this.props.children
// : todo : custom error page
return ( return (
<div className='error-boundary'> <div className='error-boundary'>
<div className='error-boundary__container'> <div className='error-boundary__container'>
<FormattedMessage id='alert.unexpected.message' defaultMessage='Error' />
<a className='error-boundary__link' href='/home'>Return Home</a> <a className='error-boundary__link' href='/home'>Return Home</a>
</div> </div>
</div> </div>
); )
} }
} }

View File

@ -1 +1 @@
export { default } from './extended_video_player'; export { default } from './extended_video_player'

View File

@ -1 +1 @@
export { default } from './footer_bar'; export { default } from './footer_bar'

View File

@ -33,6 +33,8 @@ export default class Icon extends PureComponent {
return <I.DissenterIcon {...options} /> return <I.DissenterIcon {...options} />
case 'ellipsis': case 'ellipsis':
return <I.EllipsisIcon {...options} /> return <I.EllipsisIcon {...options} />
case 'error':
return <I.ErrorIcon {...options} />
case 'globe': case 'globe':
return <I.GlobeIcon {...options} /> return <I.GlobeIcon {...options} />
case 'group': case 'group':
@ -41,16 +43,24 @@ export default class Icon extends PureComponent {
return <I.HomeIcon {...options} /> return <I.HomeIcon {...options} />
case 'like': case 'like':
return <I.LikeIcon {...options} /> return <I.LikeIcon {...options} />
case 'link':
return <I.LinkIcon {...options} />
case 'list': case 'list':
return <I.ListIcon {...options} /> return <I.ListIcon {...options} />
case 'loading': case 'loading':
return <I.LoadingIcon {...options} /> return <I.LoadingIcon {...options} />
case 'more':
return <I.MoreIcon {...options} />
case 'media': case 'media':
return <I.MediaIcon {...options} /> return <I.MediaIcon {...options} />
case 'missing':
return <I.MissingIcon {...options} />
case 'more':
return <I.MoreIcon {...options} />
case 'notifications': case 'notifications':
return <I.NotificationsIcon {...options} /> return <I.NotificationsIcon {...options} />
case 'pin':
return <I.PinIcon {...options} />
case 'play':
return <I.PlayIcon {...options} />
case 'poll': case 'poll':
return <I.PollIcon {...options} /> return <I.PollIcon {...options} />
case 'repost': case 'repost':

View File

@ -1 +1 @@
export { default } from './icon_button'; export { default } from './icon_button'

View File

@ -1 +1 @@
export { default } from './image_loader'; export { default } from './image_loader'

View File

@ -1,5 +1,4 @@
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Button from './button'
import Icon from './icon' import Icon from './icon'
const cx = classNames.bind(_s) const cx = classNames.bind(_s)

View File

@ -1,5 +1,7 @@
import { injectIntl, defineMessages } from 'react-intl' import { injectIntl, defineMessages } from 'react-intl'
import Button from './button'
import Icon from './icon' import Icon from './icon'
import Text from './text'
const messages = defineMessages({ const messages = defineMessages({
load_more: { id: 'status.load_more', defaultMessage: 'Load more' }, load_more: { id: 'status.load_more', defaultMessage: 'Load more' },
@ -31,16 +33,29 @@ class LoadMore extends PureComponent {
const { disabled, visible, gap, intl } = this.props const { disabled, visible, gap, intl } = this.props
return ( return (
<button <Button
className={[_s.default].join(' ')} block
radiusSmall
backgroundColor='tertiary'
color='primary'
disabled={disabled || !visible} disabled={disabled || !visible}
style={{ visibility: visible ? 'visible' : 'hidden' }} style={{ visibility: visible ? 'visible' : 'hidden' }}
onClick={this.handleClick} onClick={this.handleClick}
aria-label={intl.formatMessage(messages.load_more)} aria-label={intl.formatMessage(messages.load_more)}
> >
{!gap && intl.formatMessage(messages.load_more)} {
{gap && <Icon id='ellipsis-h' />} !gap &&
</button> <Text color='inherit'>
{intl.formatMessage(messages.load_more)}
</Text>
}
{
gap &&
<Text align='center'>
<Icon id='ellipsis' />
</Text>
}
</Button>
) )
} }

View File

@ -1 +1 @@
export { default } from './media_gallery'; export { default } from './media_gallery'

View File

@ -6,6 +6,7 @@ import { cancelReplyCompose } from '../../actions/compose'
const messages = defineMessages({ const messages = defineMessages({
confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
delete: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
}) })
const mapStateToProps = state => ({ const mapStateToProps = state => ({
@ -58,7 +59,7 @@ class ModalBase extends PureComponent {
if (!composeId && composeText && type == 'COMPOSE') { if (!composeId && composeText && type == 'COMPOSE') {
onOpenModal('CONFIRM', { onOpenModal('CONFIRM', {
message: <FormattedMessage id='confirmations.delete.message' defaultMessage='Are you sure you want to delete this status?' />, message: intl.formatMessage(messages.delete),
confirm: intl.formatMessage(messages.confirm), confirm: intl.formatMessage(messages.confirm),
onConfirm: () => onCancelReplyCompose(), onConfirm: () => onCancelReplyCompose(),
onCancel: () => onOpenModal('COMPOSE'), onCancel: () => onOpenModal('COMPOSE'),

View File

@ -1,13 +1,18 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import { me } from '../../initial_state'
import { shortNumberFormat } from '../../utils/numbers' import { shortNumberFormat } from '../../utils/numbers'
import PanelLayout from './panel_layout' import PanelLayout from './panel_layout'
import UserStat from '../user_stat' import Button from '../button'
import Divider from '../divider'
import Heading from '../heading'
import Icon from '../icon'
import Text from '../text'
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'about', defaultMessage: 'About' }, title: { id: 'about', defaultMessage: 'About' },
members: { id: 'members', defaultMessage: 'Members' },
}) })
export default export default
@ -24,7 +29,45 @@ class GroupInfoPanel extends ImmutablePureComponent {
return ( return (
<PanelLayout title={intl.formatMessage(messages.title)}> <PanelLayout title={intl.formatMessage(messages.title)}>
{
!!group &&
<Fragment>
<Heading size='h3'>
{group.get('title')}
</Heading>
<Divider small />
<div className={[_s.default, _s.flexRow, _s.justifyContentCenter].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Icon id='group' height='14px' width='14px' />
<Text size='small' className={_s.marginLeft5PX}>
{intl.formatMessage(messages.members)}
</Text>
</div>
<Button
text
to={`/groups/${group.get('id')}/members`}
color='brand'
backgroundColor='none'
className={_s.marginLeftAuto}
>
<Text color='inherit' weight='medium' size='normal' className={_s.underline_onHover}>
{shortNumberFormat(group.get('member_count'))}
&nbsp;
{intl.formatMessage(messages.members)}
</Text>
</Button>
</div>
<Divider small />
<Text>
{group.get('description')}
</Text>
</Fragment>
}
</PanelLayout> </PanelLayout>
) )
} }

View File

@ -1,15 +1,14 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions' import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
import ImmutablePureComponent from 'react-immutable-pure-component' import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes' import ImmutablePropTypes from 'react-immutable-proptypes'
import { List as ImmutableList } from 'immutable' import { List as ImmutableList } from 'immutable'
import classNames from 'classnames/bind'
import PanelLayout from './panel_layout' import PanelLayout from './panel_layout'
import Divider from '../divider'
import Icon from '../icon' import Icon from '../icon'
import Text from '../text' import Text from '../text'
const cx = classNames.bind(_s)
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'about', defaultMessage: 'About' }, title: { id: 'about', defaultMessage: 'About' },
linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' }, linkVerifiedOn: { id: 'account.link_verified_on', defaultMessage: 'Ownership of this link was checked on {date}' },
@ -19,12 +18,12 @@ const messages = defineMessages({
}) })
const mapStateToProps = (state, { account }) => { const mapStateToProps = (state, { account }) => {
const identityProofs = !!account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList(); const identityProofs = !!account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList()
return { return {
identityProofs, identityProofs,
domain: state.getIn(['meta', 'domain']), domain: state.getIn(['meta', 'domain']),
}; }
}; }
const mapDispatchToProps = dispatch => { const mapDispatchToProps = dispatch => {
@ -54,29 +53,9 @@ class ProfileInfoPanel extends ImmutablePureComponent {
const fields = !account ? null : account.get('fields') const fields = !account ? null : account.get('fields')
const content = !account ? null : { __html: account.get('note_emojified') } const content = !account ? null : { __html: account.get('note_emojified') }
const memberSinceDate = !account ? null : intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' }); const memberSinceDate = !account ? null : intl.formatDate(account.get('created_at'), { month: 'long', year: 'numeric' })
const hasNote = !!content ? (account.get('note').length > 0 && account.get('note') !== '<p></p>') : false const hasNote = !!content ? (account.get('note').length > 0 && account.get('note') !== '<p></p>') : false
const lineClasses = cx({
default: 1,
flexRow: 1,
alignItemsCenter: 1,
borderTop1PX: 1,
borderColorSecondary: 1,
marginTop10PX: 1,
paddingTop10PX: 1,
})
const memberLineClasses = cx({
default: 1,
flexRow: 1,
alignItemsCenter: 1,
borderTop1PX: hasNote,
borderColorSecondary: hasNote,
marginTop10PX: hasNote,
paddingTop10PX: hasNote,
})
return ( return (
<PanelLayout title={intl.formatMessage(messages.title)}> <PanelLayout title={intl.formatMessage(messages.title)}>
{ {
@ -84,10 +63,13 @@ class ProfileInfoPanel extends ImmutablePureComponent {
<div className={[_s.default].join(' ')}> <div className={[_s.default].join(' ')}>
{ {
hasNote && hasNote &&
<div className={_s.dangerousContent} dangerouslySetInnerHTML={content} /> <Fragment>
<div className={_s.dangerousContent} dangerouslySetInnerHTML={content} />
<Divider small />
</Fragment>
} }
<div className={memberLineClasses}> <div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Icon id='calendar' width='12px' height='12px' className={_s.fillcolorSecondary} /> <Icon id='calendar' width='12px' height='12px' className={_s.fillcolorSecondary} />
<Text <Text
size='small' size='small'
@ -104,45 +86,50 @@ class ProfileInfoPanel extends ImmutablePureComponent {
{(fields.size > 0 || identityProofs.size > 0) && ( {(fields.size > 0 || identityProofs.size > 0) && (
<div className={[_s.default]}> <div className={[_s.default]}>
{identityProofs.map((proof, i) => ( {identityProofs.map((proof, i) => (
<dl className={lineClasses} key={`profile-identity-proof-${i}`}> <Fragment>
<dt <Divider small />
className={_s.dangerousContent} <dl className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')} key={`profile-identity-proof-${i}`}>
dangerouslySetInnerHTML={{ __html: proof.get('provider') }} <dt
/> className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: proof.get('provider') }}
/>
{ /* : todo : */ } { /* : todo : */}
<dd className='verified'> <dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'> <a href={proof.get('proof_url')} target='_blank' rel='noopener'>
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}> <span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' /> <Icon id='check' className='verified__mark' />
</span> </span>
</a> </a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'> <a href={proof.get('profile_url')} target='_blank' rel='noopener'>
<span <span
className={_s.dangerousContent} className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }} dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
/> />
</a> </a>
</dd> </dd>
</dl> </dl>
</Fragment>
))} ))}
{ {
fields.map((pair, i) => ( fields.map((pair, i) => (
<dl className={lineClasses} key={`profile-field-${i}`}> <Fragment>
<dt <Divider small />
className={[_s.text, _s.dangerousContent].join('')} <dl className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')} key={`profile-field-${i}`}>
dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }} <dt
title={pair.get('name')} className={[_s.text, _s.dangerousContent].join('')}
/> dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }}
<dd title={pair.get('name')}
className={[_s.dangerousContent, _s.marginLeftAuto].join(' ')} />
title={pair.get('value_plain')} <dd
dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }} className={[_s.dangerousContent, _s.marginLeftAuto].join(' ')}
/> title={pair.get('value_plain')}
</dl> dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }}
/>
</dl>
</Fragment>
)) ))
} }

View File

@ -42,7 +42,7 @@ class WhoToFollowPanel extends ImmutablePureComponent {
const { intl, /* suggestions, */ dismissSuggestion } = this.props; const { intl, /* suggestions, */ dismissSuggestion } = this.props;
// : testing!!! : // : testing!!! :
const suggestions = [ const suggestions = [
"1", "1","1","1",
] ]
// if (suggestions.isEmpty()) { // if (suggestions.isEmpty()) {
// return null; // return null;

View File

@ -1 +1 @@
export { default } from './poll'; export { default } from './poll'

View File

@ -135,10 +135,10 @@ class PopoverBase extends ImmutablePureComponent {
return ( return (
<div onKeyDown={this.handleKeyDown} className={containerClasses}> <div onKeyDown={this.handleKeyDown} className={containerClasses}>
<div show={open} placement={popoverPlacement} target={this.findTarget}> { /* <div show={open} placement={popoverPlacement} target={this.findTarget}>
{ /* <PopoverMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} /> */} <PopoverMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} />
{children} {children}
</div> </div> */}
</div> </div>
) )
} }

View File

@ -48,9 +48,9 @@ class PopoverRoot extends PureComponent {
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
} }
getSnapshotBeforeUpdate() { // getSnapshotBeforeUpdate() {
return { visible: !!this.props.type } // return { visible: !!this.props.type }
} // }
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
@ -73,25 +73,25 @@ class PopoverRoot extends PureComponent {
} }
handleDocumentClick = e => { handleDocumentClick = e => {
if (this.node && !this.node.contains(e.target)) { // if (this.node && !this.node.contains(e.target)) {
this.props.onClose() // this.props.onClose()
} // }
} }
componentDidMount() { componentDidMount() {
document.addEventListener('click', this.handleDocumentClick, false) // document.addEventListener('click', this.handleDocumentClick, false)
document.addEventListener('keydown', this.handleKeyDown, false) // document.addEventListener('keydown', this.handleKeyDown, false)
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions) // document.addEventListener('touchend', this.handleDocumentClick, listenerOptions)
if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus() // if (this.focusedItem && this.props.openedViaKeyboard) this.focusedItem.focus()
this.setState({ mounted: true }) // this.setState({ mounted: true })
} }
componentWillUnmount() { componentWillUnmount() {
document.removeEventListener('click', this.handleDocumentClick, false) // document.removeEventListener('click', this.handleDocumentClick, false)
document.removeEventListener('keydown', this.handleKeyDown, false) // document.removeEventListener('keydown', this.handleKeyDown, false)
document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions) // document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions)
} }
setRef = c => { setRef = c => {
@ -159,13 +159,15 @@ class PopoverRoot extends PureComponent {
console.log("popover root - type, visible:", type, visible) console.log("popover root - type, visible:", type, visible)
// <PopoverBase className={`popover-menu ${placement}`} visible={visible} ref={this.setRef}>
// {
// visible &&
// <UserInfoPopover />
// }
// </PopoverBase>
return ( return (
<PopoverBase className={`popover-menu ${placement}`} visible={visible} ref={this.setRef}> <div></div>
{
visible &&
<UserInfoPopover />
}
</PopoverBase>
) )
} }
} }

View File

@ -1 +1 @@
export { default } from './search_popout'; export { default } from './search_popout'

View File

@ -1 +1 @@
export { default } from './setting_toggle'; export { default } from './setting_toggle'

View File

@ -1 +1 @@
export { default } from './status'; export { default } from './status'

View File

@ -321,9 +321,9 @@ class Status extends ImmutablePureComponent {
prepend = ( prepend = (
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}> <div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}>
<Icon <Icon
id='thumb-tack' id='pin'
width='12px' width='10px'
height='12px' height='10px'
className={_s.fillcolorSecondary} className={_s.fillcolorSecondary}
/> />
<Text size='small' color='secondary' className={_s.marginLeft5PX}> <Text size='small' color='secondary' className={_s.marginLeft5PX}>

View File

@ -1 +1 @@
export { default } from './status_action_bar'; export { default } from './status_action_bar'

View File

@ -1 +1 @@
export { default } from './status_check_box'; export { default } from './status_check_box'

View File

@ -1 +1 @@
export { default } from './status_content'; export { default } from './status_content'

View File

@ -1 +1 @@
export { default } from './status_list'; export { default } from './status_list'

View File

@ -98,7 +98,7 @@ export default class StatusList extends ImmutablePureComponent {
const { statusIds, featuredStatusIds, onLoadMore, timelineId, totalQueuedItemsCount, isLoading, isPartial, withGroupAdmin, group, promotion, promotedStatus, ...other } = this.props; const { statusIds, featuredStatusIds, onLoadMore, timelineId, totalQueuedItemsCount, isLoading, isPartial, withGroupAdmin, group, promotion, promotedStatus, ...other } = this.props;
if (isPartial) { if (isPartial) {
return ( <ColumnIndicator type='loading' /> ); return <ColumnIndicator type='loading' />
} }
let scrollableContent = (isLoading || statusIds.size > 0) ? ( let scrollableContent = (isLoading || statusIds.size > 0) ? (
@ -110,7 +110,7 @@ export default class StatusList extends ImmutablePureComponent {
onClick={onLoadMore} onClick={onLoadMore}
/> />
) : ( ) : (
<React.Fragment key={statusId}> <Fragment key={statusId}>
<StatusContainer <StatusContainer
id={statusId} id={statusId}
onMoveUp={this.handleMoveUp} onMoveUp={this.handleMoveUp}
@ -120,15 +120,16 @@ export default class StatusList extends ImmutablePureComponent {
withGroupAdmin={withGroupAdmin} withGroupAdmin={withGroupAdmin}
showThread showThread
/> />
{promotedStatus && index === promotion.position && ( {
promotedStatus && index === promotion.position &&
<StatusContainer <StatusContainer
id={promotion.status_id} id={promotion.status_id}
contextType={timelineId} contextType={timelineId}
promoted promoted
showThread showThread
/> />
)} }
</React.Fragment> </Fragment>
)) ))
) : null; ) : null;

View File

@ -66,9 +66,6 @@ class TabBarItem extends PureComponent {
size: !!large ? 'normal' : 'small', size: !!large ? 'normal' : 'small',
color: isCurrent ? 'brand' : large ? 'secondary' : 'primary', color: isCurrent ? 'brand' : large ? 'secondary' : 'primary',
weight: isCurrent ? 'bold' : large ? 'medium' : 'normal', weight: isCurrent ? 'bold' : large ? 'medium' : 'normal',
className: cx({
paddingHorizontal5PX: large,
}),
} }
return ( return (

View File

@ -4,7 +4,6 @@ import { injectIntl, defineMessages } from 'react-intl'
import { me } from '../initial_state' import { me } from '../initial_state'
import ComposeFormContainer from '../features/compose/containers/compose_form_container' import ComposeFormContainer from '../features/compose/containers/compose_form_container'
import Block from './block' import Block from './block'
import Avatar from './avatar'
import Heading from './heading' import Heading from './heading'
const messages = defineMessages({ const messages = defineMessages({

View File

@ -1,6 +1,8 @@
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl'
import classNames from 'classnames'; import classNames from 'classnames'
import { shortNumberFormat } from '../utils/numbers'; import { shortNumberFormat } from '../utils/numbers'
import Button from './button'
import Text from './text'
export default class TimelineQueueButtonHeader extends PureComponent { export default class TimelineQueueButtonHeader extends PureComponent {
@ -8,21 +10,24 @@ export default class TimelineQueueButtonHeader extends PureComponent {
onClick: PropTypes.func.isRequired, onClick: PropTypes.func.isRequired,
count: PropTypes.number, count: PropTypes.number,
itemType: PropTypes.string, itemType: PropTypes.string,
}; floating: PropTypes.bool
}
static defaultProps = { static defaultProps = {
count: 0, count: 0,
itemType: 'item', itemType: 'item',
}; }
render () { render () {
const { count, itemType, onClick } = this.props; const { count, itemType, onClick } = this.props
const hasItems = (count > 0); const hasItems = count > 0
// : todo :
const classes = classNames('timeline-queue-header', { const classes = classNames('timeline-queue-header', {
'timeline-queue-header--extended': hasItems, 'timeline-queue-header--extended': hasItems,
}); })
return ( return (
<div className={classes}> <div className={classes}>
@ -40,7 +45,7 @@ export default class TimelineQueueButtonHeader extends PureComponent {
} }
</a> </a>
</div> </div>
); )
} }
} }

View File

@ -1 +1 @@
export { default } from './toggle_switch'; export { default } from './toggle_switch'

View File

@ -16,7 +16,7 @@ export default class TrendingItem extends ImmutablePureComponent {
static propTypes = { static propTypes = {
trend: ImmutablePropTypes.map.isRequired, trend: ImmutablePropTypes.map.isRequired,
}; }
state = { state = {
hovering: false, hovering: false,

View File

@ -14,7 +14,7 @@ export default class TrendingItemCard extends ImmutablePureComponent {
static propTypes = { static propTypes = {
trend: ImmutablePropTypes.map.isRequired, trend: ImmutablePropTypes.map.isRequired,
}; }
state = { state = {
hovering: false, hovering: false,

View File

@ -1 +1 @@
export { default } from './zoomable_image'; export { default } from './zoomable_image'

View File

@ -1 +1 @@
export { default } from './media_item'; export { default } from './media_item'

View File

@ -1 +1 @@
export { default } from './account_gallery'; export { default } from './account_gallery'

View File

@ -1 +1 @@
export { default } from './header'; export { default } from './header'

View File

@ -1 +1 @@
export { default } from './inner_header'; export { default } from './inner_header'

View File

@ -1 +1 @@
export { default } from './moved_note'; export { default } from './moved_note'

View File

@ -1 +1 @@
export { default } from './profile_info_panel'; export { default } from './profile_info_panel'

View File

@ -1 +1 @@
export { default } from './account_timeline'; export { default } from './account_timeline'

View File

@ -1 +1 @@
export { default } from './blocks'; export { default } from './blocks'

View File

@ -1 +1 @@
export { default } from './column_settings'; export { default } from './column_settings'

View File

@ -1 +1 @@
export { default } from './community_timeline'; export { default } from './community_timeline'

View File

@ -1 +1 @@
export { default } from './action_bar'; export { default } from './action_bar'

View File

@ -1 +1 @@
export { default } from './character_counter'; export { default } from './character_counter'

View File

@ -1 +1 @@
export { default } from './compose_form'; export { default } from './compose_form'

View File

@ -1 +1 @@
export { default } from './emoji_picker_dropdown'; export { default } from './emoji_picker_dropdown'

View File

@ -1 +1 @@
export { default } from './navigation_bar'; export { default } from './navigation_bar'

View File

@ -1 +1 @@
export { default } from './poll_form'; export { default } from './poll_form'

View File

@ -1 +1 @@
export { default } from './reply_indicator'; export { default } from './reply_indicator'

View File

@ -1 +1 @@
export { default } from './search_results'; export { default } from './search_results'

View File

@ -1 +1 @@
export { default } from './upload'; export { default } from './upload'

View File

@ -1 +1 @@
export { default } from './upload_form'; export { default } from './upload_form'

View File

@ -1 +1 @@
export { default } from './upload_progress'; export { default } from './upload_progress'

View File

@ -1 +1 @@
export { default } from './warning'; export { default } from './warning'

View File

@ -1 +1 @@
export { default } from './compose'; export { default } from './compose'

View File

@ -1 +1 @@
export { default } from './domain_blocks'; export { default } from './domain_blocks'

View File

@ -2,7 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { fetchFavoritedStatuses, expandFavoritedStatuses } from '../../actions/favourites'; import { fetchFavoritedStatuses, expandFavoritedStatuses } from '../../actions/favorites';
import { meUsername } from '../../initial_state'; import { meUsername } from '../../initial_state';
import StatusList from '../../components/status_list'; import StatusList from '../../components/status_list';
import ColumnIndicator from '../../components/column_indicator'; import ColumnIndicator from '../../components/column_indicator';
@ -10,15 +10,15 @@ import ColumnIndicator from '../../components/column_indicator';
const mapStateToProps = (state, { params: { username } }) => { const mapStateToProps = (state, { params: { username } }) => {
return { return {
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()), isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
statusIds: state.getIn(['status_lists', 'favourites', 'items']), statusIds: state.getIn(['status_lists', 'favorites', 'items']),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true), isLoading: state.getIn(['status_lists', 'favorites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']), hasMore: !!state.getIn(['status_lists', 'favorites', 'next']),
}; };
}; };
export default export default
@connect(mapStateToProps) @connect(mapStateToProps)
class Favourites extends ImmutablePureComponent { class Favorites extends ImmutablePureComponent {
static propTypes = { static propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
@ -46,11 +46,11 @@ class Favourites extends ImmutablePureComponent {
return ( return (
<StatusList <StatusList
statusIds={statusIds} statusIds={statusIds}
scrollKey='favourited_statuses' scrollKey='favorited_statuses'
hasMore={hasMore} hasMore={hasMore}
isLoading={isLoading} isLoading={isLoading}
onLoadMore={this.handleLoadMore} onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favourite gabs yet. When you favourite one, it will show up here." />} emptyMessage={<FormattedMessage id='empty_column.favorited_statuses' defaultMessage="You don't have any favorite gabs yet. When you favorite one, it will show up here." />}
/> />
); );
} }

View File

@ -0,0 +1 @@
export { default } from './favorited_statuses'

View File

@ -1 +0,0 @@
export { default } from './favourited_statuses';

View File

@ -1 +1 @@
export { default } from './account_authorize'; export { default } from './account_authorize'

View File

@ -1 +1 @@
export { default } from './follow_requests'; export { default } from './follow_requests'

View File

@ -1,34 +1,36 @@
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'; import { debounce } from 'lodash'
import { FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl'
import ColumnIndicator from '../../components/column_indicator'; import ColumnIndicator from '../../components/column_indicator'
import { import {
fetchAccount, fetchAccount,
fetchFollowers, fetchFollowers,
expandFollowers, expandFollowers,
fetchAccountByUsername, fetchAccountByUsername,
} from '../../actions/accounts'; } from '../../actions/accounts'
import { me } from '../../initial_state'; import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container'
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => { const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts']); const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase()); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1; let accountId = -1
if (accountFetchError) { if (accountFetchError) {
accountId = null; accountId = null
} else { } else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase()); let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1; accountId = account ? account.getIn(['id'], null) : -1
} }
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false); const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false); const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false); const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing)); const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return { return {
accountId, accountId,
@ -36,69 +38,93 @@ const mapStateToProps = (state, { params: { username } }) => {
isAccount: !!state.getIn(['accounts', accountId]), isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']), accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'followers', accountId, 'next']),
}; }
}; }
const messages = defineMessages({
followers: { id: 'account.followers', defaultMessage: 'Followers' },
empty: { id: 'account.followers.empty', defaultMessage: 'No one follows this user yet.' },
unavailable: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
})
export default export default
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl
class Followers extends ImmutablePureComponent { class Followers extends ImmutablePureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
unavailable: PropTypes.bool, unavailable: PropTypes.bool,
}; }
componentWillMount() { componentWillMount() {
const { params: { username }, accountId } = this.props; const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) { if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowers(accountId)); this.props.dispatch(fetchFollowers(accountId))
} else { } else {
this.props.dispatch(fetchAccountByUsername(username)); this.props.dispatch(fetchAccountByUsername(username))
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) { if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.accountId)); this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowers(nextProps.accountId)); this.props.dispatch(fetchFollowers(nextProps.accountId))
} }
} }
handleLoadMore = debounce(() => { handleLoadMore = debounce(() => {
if (this.props.accountId && this.props.accountId !== -1) { if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandFollowers(this.props.accountId)); this.props.dispatch(expandFollowers(this.props.accountId))
} }
}, 300, { leading: true }); }, 300, { leading: true })
render() { render() {
const { accountIds, hasMore, isAccount, accountId, unavailable } = this.props; const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) { if (!isAccount && accountId !== -1) {
return (<ColumnIndicator type='missing' />); return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) { } else if (accountId === -1 || (!accountIds)) {
return (<ColumnIndicator type='loading' />); return <ColumnIndicator type='loading' />
} else if (unavailable) { } else if (unavailable) {
return (<ColumnIndicator type='error' message={<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />} />); return <ColumnIndicator type='error' message={intl.formatMessage(messages.unavailable)} />
} }
return ( return (
<ScrollableList <Block>
scrollKey='followers' <div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
hasMore={hasMore} <Heading size='h3'>
onLoadMore={this.handleLoadMore} {intl.formatMessage(messages.followers)}
emptyMessage={<FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />} </Heading>
> </div>
{accountIds.map(id => <div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<AccountContainer key={id} id={id} withNote={false} /> <ScrollableList
)} scrollKey='followers'
</ScrollableList> hasMore={hasMore}
); onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{accountIds.map((id, i) => (
<AccountContainer key={id} id={id} withNote={false} compact />
))}
</ScrollableList>
</div>
</Block>
)
} }
} }

View File

@ -1 +1 @@
export { default } from './followers'; export { default } from './followers'

View File

@ -1,34 +1,36 @@
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'; import { debounce } from 'lodash'
import { FormattedMessage } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl'
import { import {
fetchAccount, fetchAccount,
fetchFollowing, fetchFollowing,
expandFollowing, expandFollowing,
fetchAccountByUsername, fetchAccountByUsername,
} from '../../actions/accounts'; } from '../../actions/accounts'
import { me } from '../../initial_state'; import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'; import AccountContainer from '../../containers/account_container'
import ColumnIndicator from '../../components/column_indicator'; import ColumnIndicator from '../../components/column_indicator'
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => { const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts']); const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase()); const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1; let accountId = -1
if (accountFetchError) { if (accountFetchError) {
accountId = null; accountId = null
} else { } else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase()); let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1; accountId = account ? account.getIn(['id'], null) : -1
} }
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false); const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false); const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false); const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing)); const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return { return {
accountId, accountId,
@ -36,71 +38,93 @@ const mapStateToProps = (state, { params: { username } }) => {
isAccount: !!state.getIn(['accounts', accountId]), isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']), accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']), hasMore: !!state.getIn(['user_lists', 'following', accountId, 'next']),
}; }
}; }
const messages = defineMessages({
follows: { id: 'account.follows', defaultMessage: 'Follows' },
empty: { id: 'account.follows.empty', defaultMessage: 'This user doesn\'t follow anyone yet.' },
unavailable: { id: 'empty_column.account_unavailable', defaultMessage: 'Profile unavailable' },
})
export default export default
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl
class Following extends ImmutablePureComponent { class Following extends ImmutablePureComponent {
static propTypes = { static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool, hasMore: PropTypes.bool,
isAccount: PropTypes.bool, isAccount: PropTypes.bool,
unavailable: PropTypes.bool, unavailable: PropTypes.bool,
}; }
componentWillMount () { componentWillMount() {
const { params: { username }, accountId } = this.props; const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) { if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId)); this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowing(accountId)); this.props.dispatch(fetchFollowing(accountId))
} else { } else {
this.props.dispatch(fetchAccountByUsername(username)); this.props.dispatch(fetchAccountByUsername(username))
} }
} }
componentWillReceiveProps (nextProps) { componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) { if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.accountId)); this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowing(nextProps.accountId)); this.props.dispatch(fetchFollowing(nextProps.accountId))
} }
} }
handleLoadMore = debounce(() => { handleLoadMore = debounce(() => {
if (this.props.accountId && this.props.accountId !== -1) { if (this.props.accountId && this.props.accountId !== -1) {
this.props.dispatch(expandFollowing(this.props.accountId)); this.props.dispatch(expandFollowing(this.props.accountId))
} }
}, 300, { leading: true }); }, 300, { leading: true })
render () { render() {
const { accountIds, hasMore, isAccount, accountId, unavailable } = this.props; const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) { if (!isAccount && accountId !== -1) {
return ( <ColumnIndicator type='missing' /> ); return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) { } else if (accountId === -1 || (!accountIds)) {
return ( <ColumnIndicator type='loading' /> ); return <ColumnIndicator type='loading' />
} else if (unavailable) { } else if (unavailable) {
return (<ColumnIndicator type='error' message={<FormattedMessage id='empty_column.account_unavailable' defaultMessage='Profile unavailable' />} />); return <ColumnIndicator type='error' message={intl.formatMessage(messages.unavailable)} />
} }
return ( return (
<ScrollableList <Block>
scrollKey='following' <div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
hasMore={hasMore} <Heading size='h3'>
onLoadMore={this.handleLoadMore} {intl.formatMessage(messages.follows)}
emptyMessage={<FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />} </Heading>
> </div>
{accountIds.map(id => <div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<AccountContainer key={id} id={id} withNote={false} /> <ScrollableList
)} scrollKey='following'
</ScrollableList> hasMore={hasMore}
); onLoadMore={this.handleLoadMore}
emptyMessage={intl.formatMessage(messages.empty)}
>
{accountIds.map((id, i) => (
<AccountContainer key={id} id={id} withNote={false} compact />
))}
</ScrollableList>
</div>
</Block>
)
} }
} }

View File

@ -1 +1 @@
export { default } from './following'; export { default } from './following'

View File

@ -1 +1 @@
export { default } from './generic_not_found'; export { default } from './generic_not_found'

View File

@ -1,7 +1,9 @@
import { changeValue, submit, reset } from '../../../actions/group_editor'; import { defineMessages, injectIntl } from 'react-intl'
import Icon from '../../../components/icon'; import { changeValue, submit, reset } from '../../actions/group_editor'
import { defineMessages, injectIntl } from 'react-intl'; import Block from '../../components/block'
import classNames from 'classnames'; import Button from '../../components/button'
import Icon from '../../components/icon'
import Input from '../../components/icon'
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'groups.form.title', defaultMessage: 'Enter a new group title' }, title: { id: 'groups.form.title', defaultMessage: 'Enter a new group title' },
@ -9,14 +11,14 @@ const messages = defineMessages({
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload a banner image' }, coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload a banner image' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' }, coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
create: { id: 'groups.form.create', defaultMessage: 'Create group' }, create: { id: 'groups.form.create', defaultMessage: 'Create group' },
}); })
const mapStateToProps = state => ({ const mapStateToProps = state => ({
title: state.getIn(['group_editor', 'title']), title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']), description: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']), coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']), disabled: state.getIn(['group_editor', 'isSubmitting']),
}); })
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onTitleChange: value => dispatch(changeValue('title', value)), onTitleChange: value => dispatch(changeValue('title', value)),
@ -24,7 +26,7 @@ const mapDispatchToProps = dispatch => ({
onCoverImageChange: value => dispatch(changeValue('coverImage', value)), onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)), onSubmit: routerHistory => dispatch(submit(routerHistory)),
reset: () => dispatch(reset()), reset: () => dispatch(reset()),
}); })
export default export default
@connect(mapStateToProps, mapDispatchToProps) @connect(mapStateToProps, mapDispatchToProps)
@ -43,69 +45,70 @@ class Create extends PureComponent {
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired, onTitleChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
}; }
componentWillMount() { componentWillMount() {
this.props.reset(); this.props.reset()
} }
handleTitleChange = e => { handleTitleChange = e => {
this.props.onTitleChange(e.target.value); this.props.onTitleChange(e.target.value)
} }
handleDescriptionChange = e => { handleDescriptionChange = e => {
this.props.onDescriptionChange(e.target.value); this.props.onDescriptionChange(e.target.value)
} }
handleCoverImageChange = e => { handleCoverImageChange = e => {
this.props.onCoverImageChange(e.target.files[0]); this.props.onCoverImageChange(e.target.files[0])
} }
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault()
this.props.onSubmit(this.context.router.history); this.props.onSubmit(this.context.router.history)
} }
render () { render() {
const { title, description, coverImage, disabled, intl } = this.props; const { title, description, coverImage, disabled, intl } = this.props
return ( return (
<form className='group-form' onSubmit={this.handleSubmit}> <Block>
<div> <form className='group-form' onSubmit={this.handleSubmit}>
<input <div>
className='standard' <Input
type='text' type='text'
value={title} value={title}
disabled={disabled} disabled={disabled}
onChange={this.handleTitleChange} onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)} placeholder={intl.formatMessage(messages.title)}
/> />
</div> </div>
<div> <div>
<textarea <textarea
className='standard' className='standard'
type='text' type='text'
value={description} value={description}
disabled={disabled} disabled={disabled}
onChange={this.handleDescriptionChange} onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)} placeholder={intl.formatMessage(messages.description)}
/> />
</div> </div>
<div> <div>
<label htmlFor='group_cover_image' className={classNames('group-form__file-label', { 'group-form__file-label--selected': coverImage !== null })}> <label htmlFor='group_cover_image' className='group-form__file-label--selected'>
{intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)} {intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
</label> </label>
<input <input
type='file' type='file'
className='group-form__file' className='group-form__file'
id='group_cover_image' id='group_cover_image'
disabled={disabled} disabled={disabled}
onChange={this.handleCoverImageChange} onChange={this.handleCoverImageChange}
/> />
<button className='button'>{intl.formatMessage(messages.create)}</button> <button className='button'>{intl.formatMessage(messages.create)}</button>
</div> </div>
</form> </form>
); </Block>
)
} }
} }

View File

@ -0,0 +1 @@
export { default } from './group_create'

View File

@ -0,0 +1,83 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import {
fetchMembers,
expandMembers,
updateRole,
createRemovedAccount,
} from '../../actions/groups';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../containers/account_container';
import ScrollableList from '../../components/scrollable_list';
const mapStateToProps = (state, { params: { id } }) => ({
group: state.getIn(['groups', id]),
relationships: state.getIn(['group_relationships', id]),
accountIds: state.getIn(['user_lists', 'groups', id, 'items']),
hasMore: !!state.getIn(['user_lists', 'groups', id, 'next']),
});
export default
@connect(mapStateToProps)
class GroupMembers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
};
componentWillMount() {
const { params: { id } } = this.props;
this.props.dispatch(fetchMembers(id));
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.props.dispatch(fetchMembers(nextProps.params.id));
}
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandMembers(this.props.params.id));
}, 300, { leading: true });
render() {
const { accountIds, hasMore, group, relationships, dispatch } = this.props;
if (!group || !accountIds || !relationships) {
return <LoadingIndicator />
}
return (
<ScrollableList
scrollKey='members'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='group.members.empty' defaultMessage='This group does not has any members.' />}
>
{accountIds.map(id => {
let menu = [];
if (relationships.get('admin')) {
menu = [
{ text: 'Remove from group', action: () => dispatch(createRemovedAccount(group.get('id'), id)) },
{ text: 'Make administrator', action: () => dispatch(updateRole(group.get('id'), id, 'admin')) },
]
}
return (
<div className="group-account-wrapper" key={id}>
<AccountContainer id={id} withNote={false} actionIcon="none" onActionClick={() => true} />
{menu.length > 0 && <DropdownMenuContainer items={menu} icon='ellipsis-h' size={18} direction='right' />}
</div>
);
})}
</ScrollableList>
);
}
}

View File

@ -0,0 +1 @@
export { default } from './group_members'

View File

@ -1,15 +1,15 @@
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import ColumnIndicator from '../../../components/column_indicator'; import ColumnIndicator from '../../components/column_indicator';
import { import {
fetchRemovedAccounts, fetchRemovedAccounts,
expandRemovedAccounts, expandRemovedAccounts,
removeRemovedAccount, removeRemovedAccount,
} from '../../../actions/groups'; } from '../../actions/groups';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../../containers/account_container'; import AccountContainer from '../../containers/account_container';
import ScrollableList from '../../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({ const messages = defineMessages({

View File

@ -0,0 +1 @@
export { default } from './group_removed_accounts'

View File

@ -1,105 +1,98 @@
import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'; import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'; import { injectIntl, defineMessages } from 'react-intl'
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom'
import classNames from 'classnames'; import classNames from 'classnames'
import { fetchGroups } from '../../../actions/groups'; import { connectGroupStream } from '../../actions/streaming'
import { openModal } from '../../../actions/modal'; import { expandGroupTimeline } from '../../actions/timelines'
import { me } from '../../../initial_state'; import StatusListContainer from '../../containers/status_list_container'
import GroupCard from './card'; // import ColumnSettingsContainer from './containers/column_settings_container'
import GroupCreate from '../create'; import ColumnIndicator from '../../components/column_indicator'
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.groups', defaultMessage: 'Groups' }, tabLatest: { id: 'group.timeline.tab_latest', defaultMessage: 'Latest' },
create: { id: 'groups.create', defaultMessage: 'Create group' }, show: { id: 'group.timeline.show_settings', defaultMessage: 'Show settings' },
tab_featured: { id: 'groups.tab_featured', defaultMessage: 'Featured' }, hide: { id: 'group.timeline.hide_settings', defaultMessage: 'Hide settings' },
tab_member: { id: 'groups.tab_member', defaultMessage: 'Member' }, empty: { id: 'empty_column.group', defaultMessage: 'There is nothing in this group yet.\nWhen members of this group post new statuses, they will appear here.' },
tab_admin: { id: 'groups.tab_admin', defaultMessage: 'Manage' }, })
});
const mapStateToProps = (state, { activeTab }) => ({ const mapStateToProps = (state, props) => ({
groupIds: state.getIn(['group_lists', activeTab]), group: state.getIn(['groups', props.params.id]),
account: state.getIn(['accounts', me]), relationships: state.getIn(['group_relationships', props.params.id]),
}); hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
})
export default export default
@connect(mapStateToProps) @connect(mapStateToProps)
@injectIntl @injectIntl
class Groups extends ImmutablePureComponent { class GroupTimeline extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
activeTab: PropTypes.string.isRequired,
showCreateForm: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
groups: ImmutablePropTypes.map,
groupIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
componentWillMount () { static contextTypes = {
this.props.dispatch(fetchGroups(this.props.activeTab)); router: PropTypes.object,
} }
componentDidUpdate(oldProps) { static propTypes = {
if (this.props.activeTab && this.props.activeTab !== oldProps.activeTab) { params: PropTypes.object.isRequired,
this.props.dispatch(fetchGroups(this.props.activeTab)); dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
hasUnread: PropTypes.bool,
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
relationships: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
}
state = {
collapsed: true,
}
componentDidMount() {
const { dispatch } = this.props
const { id } = this.props.params
dispatch(expandGroupTimeline(id))
this.disconnect = dispatch(connectGroupStream(id))
}
componentWillUnmount() {
if (this.disconnect) {
this.disconnect()
this.disconnect = null
} }
} }
handleOpenProUpgradeModal = () => { handleLoadMore = maxId => {
this.props.dispatch(openModal('PRO_UPGRADE')); const { id } = this.props.params
this.props.dispatch(expandGroupTimeline(id, { maxId }))
} }
renderHeader() { handleToggleClick = (e) => {
const { intl, activeTab, account, onOpenProUpgradeModal } = this.props; e.stopPropagation()
this.setState({ collapsed: !this.state.collapsed })
}
const isPro = account.get('is_pro'); render() {
const { columnId, group, relationships, account, intl } = this.props
const { collapsed } = this.state
const { id } = this.props.params
if (typeof group === 'undefined' || !relationships) {
return <ColumnIndicator type='loading' />
} else if (group === false) {
return <ColumnIndicator type='missing' />
}
return ( return (
<div className="group-column-header"> <StatusListContainer
<div className="group-column-header__cta"> alwaysPrepend
{ scrollKey={`group_timeline-${columnId}`}
account && isPro && timelineId={`group:${id}`}
<Link to="/groups/create" className="button standard-small">{intl.formatMessage(messages.create)}</Link> onLoadMore={this.handleLoadMore}
} group={group}
{ withGroupAdmin={relationships && relationships.get('admin')}
account && !isPro && emptyMessage={intl.formatMessage(messages.empty)}
<button onClick={this.handleOpenProUpgradeModal} className="button standard-small">{intl.formatMessage(messages.create)}</button> />
} )
</div>
<div className="group-column-header__title">{intl.formatMessage(messages.heading)}</div>
<div className="column-header__wrapper">
<h1 className="column-header">
<Link to='/groups' className={classNames('btn grouped', {'active': 'featured' === activeTab})}>
{intl.formatMessage(messages.tab_featured)}
</Link>
<Link to='/groups/browse/member' className={classNames('btn grouped', {'active': 'member' === activeTab})}>
{intl.formatMessage(messages.tab_member)}
</Link>
<Link to='/groups/browse/admin' className={classNames('btn grouped', {'active': 'admin' === activeTab})}>
{intl.formatMessage(messages.tab_admin)}
</Link>
</h1>
</div>
</div>
);
} }
render () { }
const { groupIds, showCreateForm } = this.props;
return (
<div>
{!showCreateForm && this.renderHeader()}
{showCreateForm && <GroupCreate /> }
<div className="group-card-list">
{groupIds.map(id => <GroupCard key={id} id={id} />)}
</div>
</div>
);
}
}

View File

@ -1 +1 @@
export { default } from './group_timeline'; export { default } from './group_timeline'

View File

@ -6,27 +6,27 @@ import ColumnIndicator from '../../../components/column_indicator';
import classNames from 'classnames'; import classNames from 'classnames';
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'groups.form.title', defaultMessage: 'Title' }, title: { id: 'groups.form.title', defaultMessage: 'Title' },
description: { id: 'groups.form.description', defaultMessage: 'Description' }, description: { id: 'groups.form.description', defaultMessage: 'Description' },
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload new banner image (optional)' }, coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload new banner image (optional)' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' }, coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
update: { id: 'groups.form.update', defaultMessage: 'Update group' }, update: { id: 'groups.form.update', defaultMessage: 'Update group' },
}); });
const mapStateToProps = (state, props) => ({ const mapStateToProps = (state, props) => ({
group: state.getIn(['groups', props.params.id]), group: state.getIn(['groups', props.params.id]),
title: state.getIn(['group_editor', 'title']), title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']), description: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']), coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']), disabled: state.getIn(['group_editor', 'isSubmitting']),
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onTitleChange: value => dispatch(changeValue('title', value)), onTitleChange: value => dispatch(changeValue('title', value)),
onDescriptionChange: value => dispatch(changeValue('description', value)), onDescriptionChange: value => dispatch(changeValue('description', value)),
onCoverImageChange: value => dispatch(changeValue('coverImage', value)), onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)), onSubmit: routerHistory => dispatch(submit(routerHistory)),
setUp: group => dispatch(setUp(group)), setUp: group => dispatch(setUp(group)),
}); });
export default export default
@ -35,103 +35,103 @@ export default
class Edit extends ImmutablePureComponent { class Edit extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
} }
static propTypes = { static propTypes = {
group: ImmutablePropTypes.map, group: ImmutablePropTypes.map,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired, description: PropTypes.string.isRequired,
coverImage: PropTypes.object, coverImage: PropTypes.object,
disabled: PropTypes.bool, disabled: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired, onTitleChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired,
}; };
componentWillMount(nextProps) { componentWillMount(nextProps) {
if (this.props.group) { if (this.props.group) {
this.props.setUp(this.props.group); this.props.setUp(this.props.group);
} }
} }
componentWillReceiveProps(nextProps) { componentWillReceiveProps(nextProps) {
if (!this.props.group && nextProps.group) { if (!this.props.group && nextProps.group) {
this.props.setUp(nextProps.group); this.props.setUp(nextProps.group);
} }
} }
handleTitleChange = e => { handleTitleChange = e => {
this.props.onTitleChange(e.target.value); this.props.onTitleChange(e.target.value);
} }
handleDescriptionChange = e => { handleDescriptionChange = e => {
this.props.onDescriptionChange(e.target.value); this.props.onDescriptionChange(e.target.value);
} }
handleCoverImageChange = e => { handleCoverImageChange = e => {
this.props.onCoverImageChange(e.target.files[0]); this.props.onCoverImageChange(e.target.files[0]);
} }
handleSubmit = e => { handleSubmit = e => {
e.preventDefault(); e.preventDefault();
this.props.onSubmit(this.context.router.history); this.props.onSubmit(this.context.router.history);
} }
handleClick = () => { handleClick = () => {
this.props.onSubmit(this.context.router.history); this.props.onSubmit(this.context.router.history);
} }
render () { render() {
const { group, title, description, coverImage, disabled, intl } = this.props; const { group, title, description, coverImage, disabled, intl } = this.props;
if (typeof group === 'undefined') { if (typeof group === 'undefined') {
return ( <ColumnIndicator type='loading' /> ); return <ColumnIndicator type='loading' />
} else if (group === false) { } else if (group === false) {
return (<ColumnIndicator type='missing' />); return <ColumnIndicator type='missing' />
} }
return ( return (
<form className='group-form' onSubmit={this.handleSubmit}> <form className='group-form' onSubmit={this.handleSubmit}>
<div> <div>
<input <input
className='standard' className='standard'
type='text' type='text'
value={title} value={title}
disabled={disabled} disabled={disabled}
onChange={this.handleTitleChange} onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)} placeholder={intl.formatMessage(messages.title)}
/> />
</div> </div>
<div> <div>
<textarea <textarea
className='standard' className='standard'
type='text' type='text'
value={description} value={description}
disabled={disabled} disabled={disabled}
onChange={this.handleDescriptionChange} onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)} placeholder={intl.formatMessage(messages.description)}
/> />
</div> </div>
<div> <div>
<label htmlFor='group_cover_image' className={classNames('group-form__file-label', { 'group-form__file-label--selected': coverImage !== null })}> <label htmlFor='group_cover_image' className={classNames('group-form__file-label', { 'group-form__file-label--selected': coverImage !== null })}>
{intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)} {intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
</label> </label>
<input <input
type='file' type='file'
className='group-form__file' className='group-form__file'
id='group_cover_image' id='group_cover_image'
disabled={disabled} disabled={disabled}
onChange={this.handleCoverImageChange} onChange={this.handleCoverImageChange}
/> />
<button>{intl.formatMessage(messages.update)}</button> <button>{intl.formatMessage(messages.update)}</button>
</div> </div>
</form> </form>
); );
} }
} }

View File

@ -1,53 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { defineMessages, injectIntl } from 'react-intl';
import { Link } from 'react-router-dom';
import { shortNumberFormat } from '../../../utils/numbers';
const messages = defineMessages({
members: { id: 'groups.card.members', defaultMessage: 'Members' },
view: { id: 'groups.card.view', defaultMessage: 'View' },
join: { id: 'groups.card.join', defaultMessage: 'Join' },
role_member: { id: 'groups.card.roles.member', defaultMessage: 'You\'re a member' },
role_admin: { id: 'groups.card.roles.admin', defaultMessage: 'You\'re an admin' },
});
const mapStateToProps = (state, { id }) => ({
group: state.getIn(['groups', id]),
relationships: state.getIn(['group_relationships', id]),
});
export default
@connect(mapStateToProps)
@injectIntl
class GroupCard extends ImmutablePureComponent {
static propTypes = {
group: ImmutablePropTypes.map,
relationships: ImmutablePropTypes.map,
}
getRole() {
const { intl, relationships } = this.props;
if (!relationships) return null;
if (relationships.get('admin')) return intl.formatMessage(messages.role_admin);
if (relationships.get('member')) return intl.formatMessage(messages.role_member);
}
render() {
const { intl, group } = this.props;
const coverImageUrl = group.get('cover_image_url');
const role = this.getRole();
return (
<Link to={`/groups/${group.get('id')}`} className="group-card">
<div className="group-card__header">{coverImageUrl && <img alt="" src={coverImageUrl} />}</div>
<div className="group-card__content">
<div className="group-card__title">{group.get('title')}</div>
<div className="group-card__meta"><strong>{shortNumberFormat(group.get('member_count'))}</strong> {intl.formatMessage(messages.members)}{role && <span> · {role}</span>}</div>
<div className="group-card__description">{group.get('description')}</div>
</div>
</Link>
);
}
}

View File

@ -1,105 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { fetchGroups } from '../../../actions/groups';
import { openModal } from '../../../actions/modal';
import { me } from '../../../initial_state';
import GroupCard from './card';
import GroupCreate from '../create';
const messages = defineMessages({
heading: { id: 'column.groups', defaultMessage: 'Groups' },
create: { id: 'groups.create', defaultMessage: 'Create group' },
tab_featured: { id: 'groups.tab_featured', defaultMessage: 'Featured' },
tab_member: { id: 'groups.tab_member', defaultMessage: 'Member' },
tab_admin: { id: 'groups.tab_admin', defaultMessage: 'Manage' },
});
const mapStateToProps = (state, { activeTab }) => ({
groupIds: state.getIn(['group_lists', activeTab]),
account: state.getIn(['accounts', me]),
});
export default
@connect(mapStateToProps)
@injectIntl
class Groups extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
activeTab: PropTypes.string.isRequired,
showCreateForm: PropTypes.bool,
dispatch: PropTypes.func.isRequired,
groups: ImmutablePropTypes.map,
groupIds: ImmutablePropTypes.list,
intl: PropTypes.object.isRequired,
};
componentWillMount () {
this.props.dispatch(fetchGroups(this.props.activeTab));
}
componentDidUpdate(oldProps) {
if (this.props.activeTab && this.props.activeTab !== oldProps.activeTab) {
this.props.dispatch(fetchGroups(this.props.activeTab));
}
}
handleOpenProUpgradeModal = () => {
this.props.dispatch(openModal('PRO_UPGRADE'));
}
renderHeader() {
const { intl, activeTab, account, onOpenProUpgradeModal } = this.props;
const isPro = account.get('is_pro');
return (
<div className="group-column-header">
<div className="group-column-header__cta">
{
account && isPro &&
<Link to="/groups/create" className="button standard-small">{intl.formatMessage(messages.create)}</Link>
}
{
account && !isPro &&
<button onClick={this.handleOpenProUpgradeModal} className="button standard-small">{intl.formatMessage(messages.create)}</button>
}
</div>
<div className="group-column-header__title">{intl.formatMessage(messages.heading)}</div>
<div className="column-header__wrapper">
<h1 className="column-header">
<Link to='/groups' className={classNames('btn grouped', {'active': 'featured' === activeTab})}>
{intl.formatMessage(messages.tab_featured)}
</Link>
<Link to='/groups/browse/member' className={classNames('btn grouped', {'active': 'member' === activeTab})}>
{intl.formatMessage(messages.tab_member)}
</Link>
<Link to='/groups/browse/admin' className={classNames('btn grouped', {'active': 'admin' === activeTab})}>
{intl.formatMessage(messages.tab_admin)}
</Link>
</h1>
</div>
</div>
);
}
render () {
const { groupIds, showCreateForm } = this.props;
return (
<div>
{!showCreateForm && this.renderHeader()}
{showCreateForm && <GroupCreate /> }
<div className="group-card-list">
{groupIds.map(id => <GroupCard key={id} id={id} />)}
</div>
</div>
);
}
}

View File

@ -1,83 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import {
fetchMembers,
expandMembers,
updateRole,
createRemovedAccount,
} from '../../../actions/groups';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../../containers/account_container';
import ScrollableList from '../../../components/scrollable_list';
const mapStateToProps = (state, { params: { id } }) => ({
group: state.getIn(['groups', id]),
relationships: state.getIn(['group_relationships', id]),
accountIds: state.getIn(['user_lists', 'groups', id, 'items']),
hasMore: !!state.getIn(['user_lists', 'groups', id, 'next']),
});
export default
@connect(mapStateToProps)
class GroupMembers extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
};
componentWillMount () {
const { params: { id } } = this.props;
this.props.dispatch(fetchMembers(id));
}
componentWillReceiveProps (nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.props.dispatch(fetchMembers(nextProps.params.id));
}
}
handleLoadMore = debounce(() => {
this.props.dispatch(expandMembers(this.props.params.id));
}, 300, { leading: true });
render () {
const { accountIds, hasMore, group, relationships, dispatch } = this.props;
if (!group || !accountIds || !relationships) {
return <LoadingIndicator />
}
return (
<ScrollableList
scrollKey='members'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='group.members.empty' defaultMessage='This group does not has any members.' />}
>
{accountIds.map(id => {
let menu = [];
if (relationships.get('admin')) {
menu = [
{ text: 'Remove from group', action: () => dispatch(createRemovedAccount(group.get('id'), id)) },
{ text: 'Make administrator', action: () => dispatch(updateRole(group.get('id'), id, 'admin')) },
]
}
return (
<div className="group-account-wrapper" key={id}>
<AccountContainer id={id} withNote={false} actionIcon="none" onActionClick={() => true} />
{menu.length > 0 && <DropdownMenuContainer items={menu} icon='ellipsis-h' size={18} direction='right' />}
</div>
);
})}
</ScrollableList>
);
}
}

View File

@ -1,99 +0,0 @@
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { injectIntl, defineMessages } from 'react-intl'
import { Link } from 'react-router-dom'
import classNames from 'classnames'
import { connectGroupStream } from '../../../actions/streaming'
import { expandGroupTimeline } from '../../../actions/timelines'
import StatusListContainer from '../../../containers/status_list_container'
import ColumnSettingsContainer from './containers/column_settings_container'
import Icon from '../../../components/icon'
import ColumnIndicator from '../../../components/column_indicator'
const messages = defineMessages({
tabLatest: { id: 'group.timeline.tab_latest', defaultMessage: 'Latest' },
show: { id: 'group.timeline.show_settings', defaultMessage: 'Show settings' },
hide: { id: 'group.timeline.hide_settings', defaultMessage: 'Hide settings' },
empty: { id: 'empty_column.group', defaultMessage: 'There is nothing in this group yet. When members of this group post new statuses, they will appear here.' },
})
const mapStateToProps = (state, props) => ({
group: state.getIn(['groups', props.params.id]),
relationships: state.getIn(['group_relationships', props.params.id]),
hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
})
export default
@connect(mapStateToProps)
@injectIntl
class GroupTimeline extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
columnId: PropTypes.string,
hasUnread: PropTypes.bool,
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
relationships: ImmutablePropTypes.map,
intl: PropTypes.object.isRequired,
}
state = {
collapsed: true,
}
componentDidMount() {
const { dispatch } = this.props
const { id } = this.props.params
dispatch(expandGroupTimeline(id))
this.disconnect = dispatch(connectGroupStream(id))
}
componentWillUnmount() {
if (this.disconnect) {
this.disconnect()
this.disconnect = null
}
}
handleLoadMore = maxId => {
const { id } = this.props.params
this.props.dispatch(expandGroupTimeline(id, { maxId }))
}
handleToggleClick = (e) => {
e.stopPropagation()
this.setState({ collapsed: !this.state.collapsed })
}
render() {
const { columnId, group, relationships, account, intl } = this.props
const { collapsed } = this.state
const { id } = this.props.params
if (typeof group === 'undefined' || !relationships) {
return (<ColumnIndicator type='loading' />)
} else if (group === false) {
return (<ColumnIndicator type='missing' />)
}
return (
<StatusListContainer
alwaysPrepend
scrollKey={`group_timeline-${columnId}`}
timelineId={`group:${id}`}
onLoadMore={this.handleLoadMore}
group={group}
withGroupAdmin={relationships && relationships.get('admin')}
emptyMessage={intl.formatMessage(messages.empty)}
/>
)
}
}

View File

@ -1 +1 @@
export { default } from './groups_collection'; export { default } from './groups_collection'

View File

@ -1 +1 @@
export { default } from './hashtag_timeline'; export { default } from './hashtag_timeline'

Some files were not shown because too many files have changed in this diff Show More