Progress
This commit is contained in:
parent
3ca4ffcc6b
commit
c6aa4e08a1
@ -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,
|
||||
};
|
||||
};
|
26
app/javascript/gabsocial/assets/error_icon.js
Normal file
26
app/javascript/gabsocial/assets/error_icon.js
Normal 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
|
@ -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,
|
||||
|
27
app/javascript/gabsocial/assets/link_icon.js
Normal file
27
app/javascript/gabsocial/assets/link_icon.js
Normal 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
|
29
app/javascript/gabsocial/assets/missing_icon.js
Normal file
29
app/javascript/gabsocial/assets/missing_icon.js
Normal 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
|
26
app/javascript/gabsocial/assets/pin_icon.js
Normal file
26
app/javascript/gabsocial/assets/pin_icon.js
Normal 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
|
26
app/javascript/gabsocial/assets/play_icon.js
Normal file
26
app/javascript/gabsocial/assets/play_icon.js
Normal 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
|
@ -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
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './account';
|
||||
export { default } from './account'
|
@ -1 +1 @@
|
||||
export { default } from './autosuggest_account';
|
||||
export { default } from './autosuggest_account'
|
@ -1 +1 @@
|
||||
export { default } from './autosuggest_emoji';
|
||||
export { default } from './autosuggest_emoji'
|
@ -1 +1 @@
|
||||
export { default } from './autosuggest_textbox';
|
||||
export { default } from './autosuggest_textbox'
|
@ -1 +1 @@
|
||||
export { default } from './bundle_column_error';
|
||||
export { default } from './bundle_column_error'
|
@ -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,
|
||||
|
@ -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>
|
||||
)
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './column_inline_form';
|
||||
export { default } from './column_inline_form'
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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} />
|
||||
)
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './domain';
|
||||
export { default } from './domain'
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './extended_video_player';
|
||||
export { default } from './extended_video_player'
|
@ -1 +1 @@
|
||||
export { default } from './footer_bar';
|
||||
export { default } from './footer_bar'
|
@ -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':
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './icon_button';
|
||||
export { default } from './icon_button'
|
@ -1 +1 @@
|
||||
export { default } from './image_loader';
|
||||
export { default } from './image_loader'
|
@ -1,5 +1,4 @@
|
||||
import classNames from 'classnames/bind'
|
||||
import Button from './button'
|
||||
import Icon from './icon'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './media_gallery';
|
||||
export { default } from './media_gallery'
|
@ -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'),
|
||||
|
@ -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'))}
|
||||
|
||||
{intl.formatMessage(messages.members)}
|
||||
</Text>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider small />
|
||||
|
||||
<Text>
|
||||
{group.get('description')}
|
||||
</Text>
|
||||
</Fragment>
|
||||
}
|
||||
</PanelLayout>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './poll';
|
||||
export { default } from './poll'
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './search_popout';
|
||||
export { default } from './search_popout'
|
@ -1 +1 @@
|
||||
export { default } from './setting_toggle';
|
||||
export { default } from './setting_toggle'
|
@ -1 +1 @@
|
||||
export { default } from './status';
|
||||
export { default } from './status'
|
@ -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}>
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './status_action_bar';
|
||||
export { default } from './status_action_bar'
|
@ -1 +1 @@
|
||||
export { default } from './status_check_box';
|
||||
export { default } from './status_check_box'
|
@ -1 +1 @@
|
||||
export { default } from './status_content';
|
||||
export { default } from './status_content'
|
@ -1 +1 @@
|
||||
export { default } from './status_list';
|
||||
export { default } from './status_list'
|
@ -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;
|
||||
|
||||
|
@ -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 (
|
||||
|
@ -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({
|
||||
|
@ -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>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './toggle_switch';
|
||||
export { default } from './toggle_switch'
|
@ -16,7 +16,7 @@ export default class TrendingItem extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
trend: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
|
@ -14,7 +14,7 @@ export default class TrendingItemCard extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
trend: ImmutablePropTypes.map.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
hovering: false,
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './zoomable_image';
|
||||
export { default } from './zoomable_image'
|
@ -1 +1 @@
|
||||
export { default } from './media_item';
|
||||
export { default } from './media_item'
|
@ -1 +1 @@
|
||||
export { default } from './account_gallery';
|
||||
export { default } from './account_gallery'
|
@ -1 +1 @@
|
||||
export { default } from './header';
|
||||
export { default } from './header'
|
@ -1 +1 @@
|
||||
export { default } from './inner_header';
|
||||
export { default } from './inner_header'
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './moved_note';
|
||||
export { default } from './moved_note'
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './profile_info_panel';
|
||||
export { default } from './profile_info_panel'
|
@ -1 +1 @@
|
||||
export { default } from './account_timeline';
|
||||
export { default } from './account_timeline'
|
@ -1 +1 @@
|
||||
export { default } from './blocks';
|
||||
export { default } from './blocks'
|
@ -1 +1 @@
|
||||
export { default } from './column_settings';
|
||||
export { default } from './column_settings'
|
@ -1 +1 @@
|
||||
export { default } from './community_timeline';
|
||||
export { default } from './community_timeline'
|
@ -1 +1 @@
|
||||
export { default } from './action_bar';
|
||||
export { default } from './action_bar'
|
@ -1 +1 @@
|
||||
export { default } from './character_counter';
|
||||
export { default } from './character_counter'
|
@ -1 +1 @@
|
||||
export { default } from './compose_form';
|
||||
export { default } from './compose_form'
|
@ -1 +1 @@
|
||||
export { default } from './emoji_picker_dropdown';
|
||||
export { default } from './emoji_picker_dropdown'
|
@ -1 +1 @@
|
||||
export { default } from './navigation_bar';
|
||||
export { default } from './navigation_bar'
|
@ -1 +1 @@
|
||||
export { default } from './poll_form';
|
||||
export { default } from './poll_form'
|
@ -1 +1 @@
|
||||
export { default } from './reply_indicator';
|
||||
export { default } from './reply_indicator'
|
@ -1 +1 @@
|
||||
export { default } from './search_results';
|
||||
export { default } from './search_results'
|
@ -1 +1 @@
|
||||
export { default } from './upload';
|
||||
export { default } from './upload'
|
@ -1 +1 @@
|
||||
export { default } from './upload_form';
|
||||
export { default } from './upload_form'
|
@ -1 +1 @@
|
||||
export { default } from './upload_progress';
|
||||
export { default } from './upload_progress'
|
@ -1 +1 @@
|
||||
export { default } from './warning';
|
||||
export { default } from './warning'
|
@ -1 +1 @@
|
||||
export { default } from './compose';
|
||||
export { default } from './compose'
|
@ -1 +1 @@
|
||||
export { default } from './domain_blocks';
|
||||
export { default } from './domain_blocks'
|
@ -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." />}
|
||||
/>
|
||||
);
|
||||
}
|
@ -0,0 +1 @@
|
||||
export { default } from './favorited_statuses'
|
@ -1 +0,0 @@
|
||||
export { default } from './favourited_statuses';
|
@ -1 +1 @@
|
||||
export { default } from './account_authorize';
|
||||
export { default } from './account_authorize'
|
@ -1 +1 @@
|
||||
export { default } from './follow_requests';
|
||||
export { default } from './follow_requests'
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1 +1 @@
|
||||
export { default } from './followers';
|
||||
export { default } from './followers'
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './following';
|
||||
export { default } from './following'
|
@ -1 +1 @@
|
||||
export { default } from './generic_not_found';
|
||||
export { default } from './generic_not_found'
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
1
app/javascript/gabsocial/features/group_create/index.js
Normal file
1
app/javascript/gabsocial/features/group_create/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './group_create'
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
1
app/javascript/gabsocial/features/group_members/index.js
Normal file
1
app/javascript/gabsocial/features/group_members/index.js
Normal file
@ -0,0 +1 @@
|
||||
export { default } from './group_members'
|
@ -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({
|
@ -0,0 +1 @@
|
||||
export { default } from './group_removed_accounts'
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './group_timeline';
|
||||
export { default } from './group_timeline'
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
@ -1 +1 @@
|
||||
export { default } from './groups_collection';
|
||||
export { default } from './groups_collection'
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user