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 { me } from '../initial_state';
export const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST';
export const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS';
export const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL';
export const FAVORITED_STATUSES_FETCH_REQUEST = 'FAVORITED_STATUSES_FETCH_REQUEST';
export const FAVORITED_STATUSES_FETCH_SUCCESS = 'FAVORITED_STATUSES_FETCH_SUCCESS';
export const FAVORITED_STATUSES_FETCH_FAIL = 'FAVORITED_STATUSES_FETCH_FAIL';
export const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST';
export const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS';
export const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL';
export const FAVORITED_STATUSES_EXPAND_REQUEST = 'FAVORITED_STATUSES_EXPAND_REQUEST';
export const FAVORITED_STATUSES_EXPAND_SUCCESS = 'FAVORITED_STATUSES_EXPAND_SUCCESS';
export const FAVORITED_STATUSES_EXPAND_FAIL = 'FAVORITED_STATUSES_EXPAND_FAIL';
export function fetchFavoritedStatuses() {
return (dispatch, getState) => {
@ -32,14 +32,14 @@ export function fetchFavoritedStatuses() {
export function fetchFavoritedStatusesRequest() {
return {
type: FAVOURITED_STATUSES_FETCH_REQUEST,
type: FAVORITED_STATUSES_FETCH_REQUEST,
skipLoading: true,
};
};
export function fetchFavoritedStatusesSuccess(statuses, next) {
return {
type: FAVOURITED_STATUSES_FETCH_SUCCESS,
type: FAVORITED_STATUSES_FETCH_SUCCESS,
statuses,
next,
skipLoading: true,
@ -48,7 +48,7 @@ export function fetchFavoritedStatusesSuccess(statuses, next) {
export function fetchFavoritedStatusesFail(error) {
return {
type: FAVOURITED_STATUSES_FETCH_FAIL,
type: FAVORITED_STATUSES_FETCH_FAIL,
error,
skipLoading: true,
};
@ -78,13 +78,13 @@ export function expandFavoritedStatuses() {
export function expandFavoritedStatusesRequest() {
return {
type: FAVOURITED_STATUSES_EXPAND_REQUEST,
type: FAVORITED_STATUSES_EXPAND_REQUEST,
};
};
export function expandFavoritedStatusesSuccess(statuses, next) {
return {
type: FAVOURITED_STATUSES_EXPAND_SUCCESS,
type: FAVORITED_STATUSES_EXPAND_SUCCESS,
statuses,
next,
};
@ -92,7 +92,7 @@ export function expandFavoritedStatusesSuccess(statuses, next) {
export function expandFavoritedStatusesFail(error) {
return {
type: FAVOURITED_STATUSES_EXPAND_FAIL,
type: FAVORITED_STATUSES_EXPAND_FAIL,
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 DissenterIcon from './dissenter_icon'
import EllipsisIcon from './ellipsis_icon'
import ErrorIcon from './error_icon'
import GlobeIcon from './globe_icon'
import GroupIcon from './group_icon'
import HomeIcon from './home_icon'
import LikeIcon from './like_icon'
import LinkIcon from './link_icon'
import ListIcon from './list_icon'
import LoadingIcon from './loading_icon'
import MediaIcon from './media_icon'
import MissingIcon from './missing_icon'
import MoreIcon from './more_icon'
import NotificationsIcon from './notifications_icon'
import PinIcon from './pin_icon'
import PlayIcon from './play_icon'
import PollIcon from './poll_icon'
import RepostIcon from './repost_icon'
import SearchIcon from './search_icon'
@ -40,15 +45,20 @@ export {
CommentIcon,
DissenterIcon,
EllipsisIcon,
ErrorIcon,
GlobeIcon,
GroupIcon,
HomeIcon,
LikeIcon,
LinkIcon,
ListIcon,
LoadingIcon,
MediaIcon,
MissingIcon,
MoreIcon,
NotificationsIcon,
PinIcon,
PlayIcon,
PollIcon,
RepostIcon,
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,
actionTitle: PropTypes.string,
onActionClick: PropTypes.func,
compact: PropTypes.bool,
}
handleFollow = () => {
@ -63,7 +64,15 @@ class Account extends ImmutablePureComponent {
}
render() {
const { account, intl, hidden, onActionClick, actionIcon, actionTitle } = this.props
const {
account,
intl,
hidden,
onActionClick,
actionIcon,
actionTitle,
compact
} = this.props
if (!account) return null
@ -76,6 +85,7 @@ class Account extends ImmutablePureComponent {
)
}
const avatarSize = compact ? 42 : 52
let buttons
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 (
<div className={[_s.default, _s.marginTop5PX, _s.marginBottom15PX].join(' ')}>
<div className={[_s.default, _s.flexRow].join(' ')}>
@ -118,7 +172,7 @@ class Account extends ImmutablePureComponent {
title={account.get('acct')}
to={`/${account.get('acct')}`}
>
<Avatar account={account} size={52} />
<Avatar account={account} size={avatarSize} />
</NavLink>
<div className={[_s.default, _s.alignItemsStart, _s.paddingHorizontal10PX].join(' ')}>
@ -129,6 +183,7 @@ class Account extends ImmutablePureComponent {
>
<DisplayName account={account} />
</NavLink>
<Button
outline
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
if (backgroundColor === 'tertiary') {
console.log("className:", className)
}
// : todo :
const classes = cx(className, {
default: 1,
@ -93,6 +97,7 @@ export default class Button extends PureComponent {
backgroundColorBrand: backgroundColor === COLORS.brand,
backgroundTransparent: backgroundColor === COLORS.none,
backgroundSubtle2: backgroundColor === COLORS.tertiary,
backgroundSubtle: backgroundColor === COLORS.secondary,
colorPrimary: color === COLORS.primary,
colorSecondary: color === COLORS.secondary,
@ -131,9 +136,9 @@ export default class Button extends PureComponent {
) : children
const options = {
disabled,
className: classes,
ref: this.setRef,
disabled: disabled,
to: to || undefined,
href: href || undefined,
onClick: this.handleClick || undefined,

View File

@ -1,5 +1,6 @@
import { defineMessages, injectIntl } from 'react-intl'
import Icon from './icon'
import Text from './text'
const messages = defineMessages({
loading: { id: 'loading_indicator.label', defaultMessage: 'Loading..' },
@ -30,12 +31,16 @@ class ColumnIndicator extends PureComponent {
return (
<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' &&
<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}
</span>
</Text>
}
</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(() => {
console.log("SHOW - USER POPOVER")
this.props.openUserInfoPopover()
// console.log("SHOW - USER POPOVER")
// this.props.openUserInfoPopover()
}, 50, { leading: true })
handleMouseLeave = () => {
console.log("HIDE - USER POPOVER")
// console.log("HIDE - USER POPOVER")
// 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 {
static propTypes = {
small: PropTypes.bool
}
render() {
const { small } = this.props
const classes = cx({
default: 1,
borderBottom1PX: 1,
borderColorSecondary2: 1,
width100PC: 1,
marginBottom15PX: !small,
marginVertical10PX: small,
})
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 {
static propTypes = {
children: PropTypes.node,
};
}
state = {
hasError: false,
@ -17,22 +15,23 @@ export default class ErrorBoundary extends PureComponent {
hasError: true,
stackTrace: error.stack,
componentStack: info && info.componentStack,
});
})
}
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 (
<div className='error-boundary'>
<div className='error-boundary__container'>
<FormattedMessage id='alert.unexpected.message' defaultMessage='Error' />
<a className='error-boundary__link' href='/home'>Return Home</a>
</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} />
case 'ellipsis':
return <I.EllipsisIcon {...options} />
case 'error':
return <I.ErrorIcon {...options} />
case 'globe':
return <I.GlobeIcon {...options} />
case 'group':
@ -41,16 +43,24 @@ export default class Icon extends PureComponent {
return <I.HomeIcon {...options} />
case 'like':
return <I.LikeIcon {...options} />
case 'link':
return <I.LinkIcon {...options} />
case 'list':
return <I.ListIcon {...options} />
case 'loading':
return <I.LoadingIcon {...options} />
case 'more':
return <I.MoreIcon {...options} />
case 'media':
return <I.MediaIcon {...options} />
case 'missing':
return <I.MissingIcon {...options} />
case 'more':
return <I.MoreIcon {...options} />
case 'notifications':
return <I.NotificationsIcon {...options} />
case 'pin':
return <I.PinIcon {...options} />
case 'play':
return <I.PlayIcon {...options} />
case 'poll':
return <I.PollIcon {...options} />
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 Button from './button'
import Icon from './icon'
const cx = classNames.bind(_s)

View File

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

View File

@ -1,13 +1,18 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { me } from '../../initial_state'
import { shortNumberFormat } from '../../utils/numbers'
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({
title: { id: 'about', defaultMessage: 'About' },
members: { id: 'members', defaultMessage: 'Members' },
})
export default
@ -24,7 +29,45 @@ class GroupInfoPanel extends ImmutablePureComponent {
return (
<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>
)
}

View File

@ -1,15 +1,14 @@
import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl'
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { List as ImmutableList } from 'immutable'
import classNames from 'classnames/bind'
import PanelLayout from './panel_layout'
import Divider from '../divider'
import Icon from '../icon'
import Text from '../text'
const cx = classNames.bind(_s)
const messages = defineMessages({
title: { id: 'about', defaultMessage: 'About' },
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 identityProofs = !!account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList();
const identityProofs = !!account ? state.getIn(['identity_proofs', account.get('id')], ImmutableList()) : ImmutableList()
return {
identityProofs,
domain: state.getIn(['meta', 'domain']),
};
};
}
}
const mapDispatchToProps = dispatch => {
@ -54,29 +53,9 @@ class ProfileInfoPanel extends ImmutablePureComponent {
const fields = !account ? null : account.get('fields')
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 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 (
<PanelLayout title={intl.formatMessage(messages.title)}>
{
@ -84,10 +63,13 @@ class ProfileInfoPanel extends ImmutablePureComponent {
<div className={[_s.default].join(' ')}>
{
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} />
<Text
size='small'
@ -104,45 +86,50 @@ class ProfileInfoPanel extends ImmutablePureComponent {
{(fields.size > 0 || identityProofs.size > 0) && (
<div className={[_s.default]}>
{identityProofs.map((proof, i) => (
<dl className={lineClasses} key={`profile-identity-proof-${i}`}>
<dt
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: proof.get('provider') }}
/>
<Fragment>
<Divider small />
<dl className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')} key={`profile-identity-proof-${i}`}>
<dt
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: proof.get('provider') }}
/>
{ /* : todo : */ }
<dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'>
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' />
</span>
</a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'>
<span
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
/>
</a>
</dd>
</dl>
{ /* : todo : */}
<dd className='verified'>
<a href={proof.get('proof_url')} target='_blank' rel='noopener'>
<span title={intl.formatMessage(messages.linkVerifiedOn, { date: intl.formatDate(proof.get('updated_at'), dateFormatOptions) })}>
<Icon id='check' className='verified__mark' />
</span>
</a>
<a href={proof.get('profile_url')} target='_blank' rel='noopener'>
<span
className={_s.dangerousContent}
dangerouslySetInnerHTML={{ __html: ' ' + proof.get('provider_username') }}
/>
</a>
</dd>
</dl>
</Fragment>
))}
{
fields.map((pair, i) => (
<dl className={lineClasses} key={`profile-field-${i}`}>
<dt
className={[_s.text, _s.dangerousContent].join('')}
dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }}
title={pair.get('name')}
/>
<dd
className={[_s.dangerousContent, _s.marginLeftAuto].join(' ')}
title={pair.get('value_plain')}
dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }}
/>
</dl>
<Fragment>
<Divider small />
<dl className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')} key={`profile-field-${i}`}>
<dt
className={[_s.text, _s.dangerousContent].join('')}
dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }}
title={pair.get('name')}
/>
<dd
className={[_s.dangerousContent, _s.marginLeftAuto].join(' ')}
title={pair.get('value_plain')}
dangerouslySetInnerHTML={{ __html: pair.get('value_emojified') }}
/>
</dl>
</Fragment>
))
}

View File

@ -42,7 +42,7 @@ class WhoToFollowPanel extends ImmutablePureComponent {
const { intl, /* suggestions, */ dismissSuggestion } = this.props;
// : testing!!! :
const suggestions = [
"1",
"1","1","1",
]
// if (suggestions.isEmpty()) {
// 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 (
<div onKeyDown={this.handleKeyDown} className={containerClasses}>
<div show={open} placement={popoverPlacement} target={this.findTarget}>
{ /* <PopoverMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} /> */}
{ /* <div show={open} placement={popoverPlacement} target={this.findTarget}>
<PopoverMenu items={items} onClose={this.handleClose} openedViaKeyboard={openedViaKeyboard} />
{children}
</div>
</div> */}
</div>
)
}

View File

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

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 = (
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}>
<Icon
id='thumb-tack'
width='12px'
height='12px'
id='pin'
width='10px'
height='10px'
className={_s.fillcolorSecondary}
/>
<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;
if (isPartial) {
return ( <ColumnIndicator type='loading' /> );
return <ColumnIndicator type='loading' />
}
let scrollableContent = (isLoading || statusIds.size > 0) ? (
@ -110,7 +110,7 @@ export default class StatusList extends ImmutablePureComponent {
onClick={onLoadMore}
/>
) : (
<React.Fragment key={statusId}>
<Fragment key={statusId}>
<StatusContainer
id={statusId}
onMoveUp={this.handleMoveUp}
@ -120,15 +120,16 @@ export default class StatusList extends ImmutablePureComponent {
withGroupAdmin={withGroupAdmin}
showThread
/>
{promotedStatus && index === promotion.position && (
{
promotedStatus && index === promotion.position &&
<StatusContainer
id={promotion.status_id}
contextType={timelineId}
promoted
showThread
/>
)}
</React.Fragment>
}
</Fragment>
))
) : null;

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ export default class TrendingItemCard extends ImmutablePureComponent {
static propTypes = {
trend: ImmutablePropTypes.map.isRequired,
};
}
state = {
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 ImmutablePureComponent from 'react-immutable-pure-component';
import { debounce } from 'lodash';
import { fetchFavoritedStatuses, expandFavoritedStatuses } from '../../actions/favourites';
import { fetchFavoritedStatuses, expandFavoritedStatuses } from '../../actions/favorites';
import { meUsername } from '../../initial_state';
import StatusList from '../../components/status_list';
import ColumnIndicator from '../../components/column_indicator';
@ -10,15 +10,15 @@ import ColumnIndicator from '../../components/column_indicator';
const mapStateToProps = (state, { params: { username } }) => {
return {
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
statusIds: state.getIn(['status_lists', 'favourites', 'items']),
isLoading: state.getIn(['status_lists', 'favourites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favourites', 'next']),
statusIds: state.getIn(['status_lists', 'favorites', 'items']),
isLoading: state.getIn(['status_lists', 'favorites', 'isLoading'], true),
hasMore: !!state.getIn(['status_lists', 'favorites', 'next']),
};
};
export default
@connect(mapStateToProps)
class Favourites extends ImmutablePureComponent {
class Favorites extends ImmutablePureComponent {
static propTypes = {
dispatch: PropTypes.func.isRequired,
@ -46,11 +46,11 @@ class Favourites extends ImmutablePureComponent {
return (
<StatusList
statusIds={statusIds}
scrollKey='favourited_statuses'
scrollKey='favorited_statuses'
hasMore={hasMore}
isLoading={isLoading}
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 ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import { FormattedMessage } from 'react-intl';
import ColumnIndicator from '../../components/column_indicator';
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import ColumnIndicator from '../../components/column_indicator'
import {
fetchAccount,
fetchFollowers,
expandFollowers,
fetchAccountByUsername,
} from '../../actions/accounts';
import { me } from '../../initial_state';
import AccountContainer from '../../containers/account_container';
import ScrollableList from '../../components/scrollable_list';
} from '../../actions/accounts'
import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'
import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts']);
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase());
const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1;
let accountId = -1
if (accountFetchError) {
accountId = null;
accountId = null
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase());
accountId = account ? account.getIn(['id'], null) : -1;
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1
}
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false);
const isLocked = state.getIn(['accounts', accountId, 'locked'], false);
const isFollowing = state.getIn(['relationships', accountId, 'following'], false);
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing));
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return {
accountId,
@ -36,69 +38,93 @@ const mapStateToProps = (state, { params: { username } }) => {
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'followers', accountId, 'items']),
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
@connect(mapStateToProps)
@injectIntl
class Followers extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
};
}
componentWillMount() {
const { params: { username }, accountId } = this.props;
const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchFollowers(accountId));
this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowers(accountId))
} else {
this.props.dispatch(fetchAccountByUsername(username));
this.props.dispatch(fetchAccountByUsername(username))
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.accountId && nextProps.accountId !== -1 && (nextProps.accountId !== this.props.accountId && nextProps.accountId)) {
this.props.dispatch(fetchAccount(nextProps.accountId));
this.props.dispatch(fetchFollowers(nextProps.accountId));
this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowers(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
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() {
const { accountIds, hasMore, isAccount, accountId, unavailable } = this.props;
const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) {
return (<ColumnIndicator type='missing' />);
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) {
return (<ColumnIndicator type='loading' />);
return <ColumnIndicator type='loading' />
} 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 (
<ScrollableList
scrollKey='followers'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='account.followers.empty' defaultMessage='No one follows this user yet.' />}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
)}
</ScrollableList>
);
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.followers)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='followers'
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 ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import { FormattedMessage } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { debounce } from 'lodash'
import { defineMessages, injectIntl } from 'react-intl'
import {
fetchAccount,
fetchFollowing,
expandFollowing,
fetchAccountByUsername,
} from '../../actions/accounts';
import { me } from '../../initial_state';
import AccountContainer from '../../containers/account_container';
import ColumnIndicator from '../../components/column_indicator';
import ScrollableList from '../../components/scrollable_list';
} from '../../actions/accounts'
import { me } from '../../initial_state'
import AccountContainer from '../../containers/account_container'
import ColumnIndicator from '../../components/column_indicator'
import ScrollableList from '../../components/scrollable_list'
import Block from '../../components/block'
import Heading from '../../components/heading'
const mapStateToProps = (state, { params: { username } }) => {
const accounts = state.getIn(['accounts']);
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase());
const accounts = state.getIn(['accounts'])
const accountFetchError = (state.getIn(['accounts', -1, 'username'], '').toLowerCase() == username.toLowerCase())
let accountId = -1;
let accountId = -1
if (accountFetchError) {
accountId = null;
accountId = null
} else {
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase());
accountId = account ? account.getIn(['id'], null) : -1;
let account = accounts.find(acct => username.toLowerCase() == acct.getIn(['acct'], '').toLowerCase())
accountId = account ? account.getIn(['id'], null) : -1
}
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false);
const isLocked = state.getIn(['accounts', accountId, 'locked'], false);
const isFollowing = state.getIn(['relationships', accountId, 'following'], false);
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing));
const isBlocked = state.getIn(['relationships', accountId, 'blocked_by'], false)
const isLocked = state.getIn(['accounts', accountId, 'locked'], false)
const isFollowing = state.getIn(['relationships', accountId, 'following'], false)
const unavailable = (me == accountId) ? false : (isBlocked || (isLocked && !isFollowing))
return {
accountId,
@ -36,71 +38,93 @@ const mapStateToProps = (state, { params: { username } }) => {
isAccount: !!state.getIn(['accounts', accountId]),
accountIds: state.getIn(['user_lists', 'following', accountId, 'items']),
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
@connect(mapStateToProps)
@injectIntl
class Following extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isAccount: PropTypes.bool,
unavailable: PropTypes.bool,
};
}
componentWillMount () {
const { params: { username }, accountId } = this.props;
componentWillMount() {
const { params: { username }, accountId } = this.props
if (accountId && accountId !== -1) {
this.props.dispatch(fetchAccount(accountId));
this.props.dispatch(fetchFollowing(accountId));
this.props.dispatch(fetchAccount(accountId))
this.props.dispatch(fetchFollowing(accountId))
} 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)) {
this.props.dispatch(fetchAccount(nextProps.accountId));
this.props.dispatch(fetchFollowing(nextProps.accountId));
this.props.dispatch(fetchAccount(nextProps.accountId))
this.props.dispatch(fetchFollowing(nextProps.accountId))
}
}
handleLoadMore = debounce(() => {
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 () {
const { accountIds, hasMore, isAccount, accountId, unavailable } = this.props;
render() {
const {
accountIds,
hasMore,
isAccount,
accountId,
unavailable,
intl
} = this.props
if (!isAccount && accountId !== -1) {
return ( <ColumnIndicator type='missing' /> );
return <ColumnIndicator type='missing' />
} else if (accountId === -1 || (!accountIds)) {
return ( <ColumnIndicator type='loading' /> );
return <ColumnIndicator type='loading' />
} 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 (
<ScrollableList
scrollKey='following'
hasMore={hasMore}
onLoadMore={this.handleLoadMore}
emptyMessage={<FormattedMessage id='account.follows.empty' defaultMessage="This user doesn't follow anyone yet." />}
>
{accountIds.map(id =>
<AccountContainer key={id} id={id} withNote={false} />
)}
</ScrollableList>
);
<Block>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.justifyContentCenter, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<Heading size='h3'>
{intl.formatMessage(messages.follows)}
</Heading>
</div>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<ScrollableList
scrollKey='following'
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 Icon from '../../../components/icon';
import { defineMessages, injectIntl } from 'react-intl';
import classNames from 'classnames';
import { defineMessages, injectIntl } from 'react-intl'
import { changeValue, submit, reset } from '../../actions/group_editor'
import Block from '../../components/block'
import Button from '../../components/button'
import Icon from '../../components/icon'
import Input from '../../components/icon'
const messages = defineMessages({
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' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
create: { id: 'groups.form.create', defaultMessage: 'Create group' },
});
})
const mapStateToProps = state => ({
title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']),
});
})
const mapDispatchToProps = dispatch => ({
onTitleChange: value => dispatch(changeValue('title', value)),
@ -24,7 +26,7 @@ const mapDispatchToProps = dispatch => ({
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)),
reset: () => dispatch(reset()),
});
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@ -43,69 +45,70 @@ class Create extends PureComponent {
intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
}
componentWillMount() {
this.props.reset();
this.props.reset()
}
handleTitleChange = e => {
this.props.onTitleChange(e.target.value);
this.props.onTitleChange(e.target.value)
}
handleDescriptionChange = e => {
this.props.onDescriptionChange(e.target.value);
this.props.onDescriptionChange(e.target.value)
}
handleCoverImageChange = e => {
this.props.onCoverImageChange(e.target.files[0]);
this.props.onCoverImageChange(e.target.files[0])
}
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.context.router.history);
e.preventDefault()
this.props.onSubmit(this.context.router.history)
}
render () {
const { title, description, coverImage, disabled, intl } = this.props;
render() {
const { title, description, coverImage, disabled, intl } = this.props
return (
<form className='group-form' onSubmit={this.handleSubmit}>
<div>
<input
className='standard'
type='text'
value={title}
disabled={disabled}
onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)}
/>
</div>
<div>
<textarea
className='standard'
type='text'
value={description}
disabled={disabled}
onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)}
/>
</div>
<div>
<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)}
</label>
<input
type='file'
className='group-form__file'
id='group_cover_image'
disabled={disabled}
onChange={this.handleCoverImageChange}
/>
<button className='button'>{intl.formatMessage(messages.create)}</button>
</div>
</form>
);
<Block>
<form className='group-form' onSubmit={this.handleSubmit}>
<div>
<Input
type='text'
value={title}
disabled={disabled}
onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)}
/>
</div>
<div>
<textarea
className='standard'
type='text'
value={description}
disabled={disabled}
onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)}
/>
</div>
<div>
<label htmlFor='group_cover_image' className='group-form__file-label--selected'>
{intl.formatMessage(coverImage === null ? messages.coverImage : messages.coverImageChange)}
</label>
<input
type='file'
className='group-form__file'
id='group_cover_image'
disabled={disabled}
onChange={this.handleCoverImageChange}
/>
<button className='button'>{intl.formatMessage(messages.create)}</button>
</div>
</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 ImmutablePropTypes from 'react-immutable-proptypes';
import { debounce } from 'lodash';
import ColumnIndicator from '../../../components/column_indicator';
import ColumnIndicator from '../../components/column_indicator';
import {
fetchRemovedAccounts,
expandRemovedAccounts,
removeRemovedAccount,
} from '../../../actions/groups';
} from '../../actions/groups';
import { FormattedMessage } from 'react-intl';
import AccountContainer from '../../../containers/account_container';
import ScrollableList from '../../../components/scrollable_list';
import AccountContainer from '../../containers/account_container';
import ScrollableList from '../../components/scrollable_list';
import { defineMessages, injectIntl } from 'react-intl';
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 { 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';
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 ColumnIndicator from '../../components/column_indicator'
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' },
});
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.\nWhen members of this group post new statuses, they will appear here.' },
})
const mapStateToProps = (state, { activeTab }) => ({
groupIds: state.getIn(['group_lists', activeTab]),
account: state.getIn(['accounts', me]),
});
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 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,
};
class GroupTimeline extends ImmutablePureComponent {
componentWillMount () {
this.props.dispatch(fetchGroups(this.props.activeTab));
static contextTypes = {
router: PropTypes.object,
}
componentDidUpdate(oldProps) {
if (this.props.activeTab && this.props.activeTab !== oldProps.activeTab) {
this.props.dispatch(fetchGroups(this.props.activeTab));
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
}
}
handleOpenProUpgradeModal = () => {
this.props.dispatch(openModal('PRO_UPGRADE'));
handleLoadMore = maxId => {
const { id } = this.props.params
this.props.dispatch(expandGroupTimeline(id, { maxId }))
}
renderHeader() {
const { intl, activeTab, account, onOpenProUpgradeModal } = this.props;
handleToggleClick = (e) => {
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 (
<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>
);
<StatusListContainer
alwaysPrepend
scrollKey={`group_timeline-${columnId}`}
timelineId={`group:${id}`}
onLoadMore={this.handleLoadMore}
group={group}
withGroupAdmin={relationships && relationships.get('admin')}
emptyMessage={intl.formatMessage(messages.empty)}
/>
)
}
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';
const messages = defineMessages({
title: { id: 'groups.form.title', defaultMessage: 'Title' },
description: { id: 'groups.form.description', defaultMessage: 'Description' },
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload new banner image (optional)' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
update: { id: 'groups.form.update', defaultMessage: 'Update group' },
title: { id: 'groups.form.title', defaultMessage: 'Title' },
description: { id: 'groups.form.description', defaultMessage: 'Description' },
coverImage: { id: 'groups.form.coverImage', defaultMessage: 'Upload new banner image (optional)' },
coverImageChange: { id: 'groups.form.coverImageChange', defaultMessage: 'Banner image selected' },
update: { id: 'groups.form.update', defaultMessage: 'Update group' },
});
const mapStateToProps = (state, props) => ({
group: state.getIn(['groups', props.params.id]),
title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']),
group: state.getIn(['groups', props.params.id]),
title: state.getIn(['group_editor', 'title']),
description: state.getIn(['group_editor', 'description']),
coverImage: state.getIn(['group_editor', 'coverImage']),
disabled: state.getIn(['group_editor', 'isSubmitting']),
});
const mapDispatchToProps = dispatch => ({
onTitleChange: value => dispatch(changeValue('title', value)),
onDescriptionChange: value => dispatch(changeValue('description', value)),
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)),
setUp: group => dispatch(setUp(group)),
onTitleChange: value => dispatch(changeValue('title', value)),
onDescriptionChange: value => dispatch(changeValue('description', value)),
onCoverImageChange: value => dispatch(changeValue('coverImage', value)),
onSubmit: routerHistory => dispatch(submit(routerHistory)),
setUp: group => dispatch(setUp(group)),
});
export default
@ -35,103 +35,103 @@ export default
class Edit extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
router: PropTypes.object,
}
static propTypes = {
group: ImmutablePropTypes.map,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
coverImage: PropTypes.object,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
group: ImmutablePropTypes.map,
title: PropTypes.string.isRequired,
description: PropTypes.string.isRequired,
coverImage: PropTypes.object,
disabled: PropTypes.bool,
intl: PropTypes.object.isRequired,
onTitleChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
};
componentWillMount(nextProps) {
if (this.props.group) {
this.props.setUp(this.props.group);
}
if (this.props.group) {
this.props.setUp(this.props.group);
}
}
componentWillReceiveProps(nextProps) {
if (!this.props.group && nextProps.group) {
this.props.setUp(nextProps.group);
}
if (!this.props.group && nextProps.group) {
this.props.setUp(nextProps.group);
}
}
handleTitleChange = e => {
this.props.onTitleChange(e.target.value);
this.props.onTitleChange(e.target.value);
}
handleDescriptionChange = e => {
this.props.onDescriptionChange(e.target.value);
this.props.onDescriptionChange(e.target.value);
}
handleCoverImageChange = e => {
this.props.onCoverImageChange(e.target.files[0]);
this.props.onCoverImageChange(e.target.files[0]);
}
handleSubmit = e => {
e.preventDefault();
this.props.onSubmit(this.context.router.history);
e.preventDefault();
this.props.onSubmit(this.context.router.history);
}
handleClick = () => {
this.props.onSubmit(this.context.router.history);
this.props.onSubmit(this.context.router.history);
}
render () {
const { group, title, description, coverImage, disabled, intl } = this.props;
render() {
const { group, title, description, coverImage, disabled, intl } = this.props;
if (typeof group === 'undefined') {
return ( <ColumnIndicator type='loading' /> );
} else if (group === false) {
return (<ColumnIndicator type='missing' />);
}
if (typeof group === 'undefined') {
return <ColumnIndicator type='loading' />
} else if (group === false) {
return <ColumnIndicator type='missing' />
}
return (
<form className='group-form' onSubmit={this.handleSubmit}>
<div>
<input
className='standard'
type='text'
value={title}
disabled={disabled}
onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)}
/>
</div>
return (
<form className='group-form' onSubmit={this.handleSubmit}>
<div>
<input
className='standard'
type='text'
value={title}
disabled={disabled}
onChange={this.handleTitleChange}
placeholder={intl.formatMessage(messages.title)}
/>
</div>
<div>
<textarea
className='standard'
type='text'
value={description}
disabled={disabled}
onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)}
/>
</div>
<div>
<textarea
className='standard'
type='text'
value={description}
disabled={disabled}
onChange={this.handleDescriptionChange}
placeholder={intl.formatMessage(messages.description)}
/>
</div>
<div>
<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)}
</label>
<div>
<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)}
</label>
<input
type='file'
className='group-form__file'
id='group_cover_image'
disabled={disabled}
onChange={this.handleCoverImageChange}
/>
<input
type='file'
className='group-form__file'
id='group_cover_image'
disabled={disabled}
onChange={this.handleCoverImageChange}
/>
<button>{intl.formatMessage(messages.update)}</button>
</div>
</form>
);
<button>{intl.formatMessage(messages.update)}</button>
</div>
</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