Progress
This commit is contained in:
parent
be3daea78b
commit
e37500c0cf
39
app/javascript/gabsocial/actions/hashtags.js
Normal file
39
app/javascript/gabsocial/actions/hashtags.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import api from '../api';
|
||||||
|
|
||||||
|
export const HASHTAGS_FETCH_REQUEST = 'HASHTAGS_FETCH_REQUEST';
|
||||||
|
export const HASHTAGS_FETCH_SUCCESS = 'HASHTAGS_FETCH_SUCCESS';
|
||||||
|
export const HASHTAGS_FETCH_FAIL = 'HASHTAGS_FETCH_FAIL';
|
||||||
|
|
||||||
|
export function fetchHashtags() {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(fetchHashtagsRequest());
|
||||||
|
|
||||||
|
api(getState).get('/api/v1/trends').then(response => {
|
||||||
|
dispatch(fetchHashtagsSuccess(response.data));
|
||||||
|
}).catch(error => dispatch(fetchHashtagsFail(error)));
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchHashtagsRequest() {
|
||||||
|
return {
|
||||||
|
type: HASHTAGS_FETCH_REQUEST,
|
||||||
|
skipLoading: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchHashtagsSuccess(tags) {
|
||||||
|
return {
|
||||||
|
tags,
|
||||||
|
type: HASHTAGS_FETCH_SUCCESS,
|
||||||
|
skipLoading: true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function fetchHashtagsFail(error) {
|
||||||
|
return {
|
||||||
|
error,
|
||||||
|
type: HASHTAGS_FETCH_FAIL,
|
||||||
|
skipLoading: true,
|
||||||
|
skipAlert: true,
|
||||||
|
};
|
||||||
|
};
|
@ -81,13 +81,17 @@ export const fetchListFail = (id, error) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const fetchLists = () => (dispatch, getState) => {
|
export const fetchLists = () => (dispatch, getState) => {
|
||||||
if (!me) return;
|
return new Promise((resolve, reject) => {
|
||||||
|
dispatch(fetchListsRequest());
|
||||||
dispatch(fetchListsRequest());
|
if (!me) return reject()
|
||||||
|
api(getState).get('/api/v1/lists').then(({ data }) => {
|
||||||
api(getState).get('/api/v1/lists')
|
dispatch(fetchListsSuccess(data))
|
||||||
.then(({ data }) => dispatch(fetchListsSuccess(data)))
|
return resolve()
|
||||||
.catch(err => dispatch(fetchListsFail(err)));
|
}).catch((err) => {
|
||||||
|
dispatch(fetchListsFail(err))
|
||||||
|
return reject()
|
||||||
|
});
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchListsRequest = () => ({
|
export const fetchListsRequest = () => ({
|
||||||
|
@ -1,39 +0,0 @@
|
|||||||
import api from '../api';
|
|
||||||
|
|
||||||
export const TRENDS_FETCH_REQUEST = 'TRENDS_FETCH_REQUEST';
|
|
||||||
export const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS';
|
|
||||||
export const TRENDS_FETCH_FAIL = 'TRENDS_FETCH_FAIL';
|
|
||||||
|
|
||||||
export function fetchTrends() {
|
|
||||||
return (dispatch, getState) => {
|
|
||||||
dispatch(fetchTrendsRequest());
|
|
||||||
|
|
||||||
api(getState).get('/api/v1/trends').then(response => {
|
|
||||||
dispatch(fetchTrendsSuccess(response.data));
|
|
||||||
}).catch(error => dispatch(fetchTrendsFail(error)));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchTrendsRequest() {
|
|
||||||
return {
|
|
||||||
type: TRENDS_FETCH_REQUEST,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchTrendsSuccess(tags) {
|
|
||||||
return {
|
|
||||||
type: TRENDS_FETCH_SUCCESS,
|
|
||||||
tags,
|
|
||||||
skipLoading: true,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fetchTrendsFail(error) {
|
|
||||||
return {
|
|
||||||
type: TRENDS_FETCH_FAIL,
|
|
||||||
error,
|
|
||||||
skipLoading: true,
|
|
||||||
skipAlert: true,
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,24 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
|
|
||||||
<div
|
|
||||||
className="account__avatar-overlay"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="account__avatar-overlay-base"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"backgroundImage": "url(/static/alice.jpg)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<div
|
|
||||||
className="account__avatar-overlay-overlay"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"backgroundImage": "url(/static/eve.jpg)",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -1,28 +0,0 @@
|
|||||||
import renderer from 'react-test-renderer';
|
|
||||||
import { fromJS } from 'immutable';
|
|
||||||
import AvatarOverlay from '../avatar_overlay';
|
|
||||||
|
|
||||||
describe('<AvatarOverlay', () => {
|
|
||||||
const account = fromJS({
|
|
||||||
username: 'alice',
|
|
||||||
acct: 'alice',
|
|
||||||
display_name: 'Alice',
|
|
||||||
avatar: '/animated/alice.gif',
|
|
||||||
avatar_static: '/static/alice.jpg',
|
|
||||||
});
|
|
||||||
|
|
||||||
const friend = fromJS({
|
|
||||||
username: 'eve',
|
|
||||||
acct: 'eve@blackhat.lair',
|
|
||||||
display_name: 'Evelyn',
|
|
||||||
avatar: '/animated/eve.gif',
|
|
||||||
avatar_static: '/static/eve.jpg',
|
|
||||||
});
|
|
||||||
|
|
||||||
it('renders a overlay avatar', () => {
|
|
||||||
const component = renderer.create(<AvatarOverlay account={account} friend={friend} />);
|
|
||||||
const tree = component.toJSON();
|
|
||||||
|
|
||||||
expect(tree).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
@ -4,7 +4,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
|
|||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { me } from '../../initial_state'
|
import { me } from '../../initial_state'
|
||||||
import Avatar from '../avatar/avatar'
|
import Avatar from '../avatar'
|
||||||
import DisplayName from '../display_name'
|
import DisplayName from '../display_name'
|
||||||
import IconButton from '../icon_button'
|
import IconButton from '../icon_button'
|
||||||
import Icon from '../icon'
|
import Icon from '../icon'
|
||||||
@ -107,33 +107,33 @@ class Account extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.marginTop5PX, styles.marginBottom15PX].join(' ')}>
|
<div className={[_s.default, _s.marginTop5PX, _s.marginBottom15PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.flexRow].join(' ')}>
|
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
className={[styles.default, styles.noUnderline].join(' ')}
|
className={[_s.default, _s.noUnderline].join(' ')}
|
||||||
title={account.get('acct')}
|
title={account.get('acct')}
|
||||||
to={`/${account.get('acct')}`}
|
to={`/${account.get('acct')}`}
|
||||||
>
|
>
|
||||||
<Avatar account={account} size={52} />
|
<Avatar account={account} size={52} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<div className={[styles.default, styles.alignItemsStart, styles.paddingHorizontal10PX].join(' ')}>
|
<div className={[_s.default, _s.alignItemsStart, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<NavLink
|
<NavLink
|
||||||
className={[styles.default, styles.noUnderline].join(' ')}
|
className={[_s.default, _s.noUnderline].join(' ')}
|
||||||
title={account.get('acct')}
|
title={account.get('acct')}
|
||||||
to={`/${account.get('acct')}`}
|
to={`/${account.get('acct')}`}
|
||||||
>
|
>
|
||||||
<DisplayName account={account} />
|
<DisplayName account={account} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<button className={[styles.default, styles.marginTop5PX, styles.colorBrand, styles.text, styles.cursorPointer, styles.fontSize14PX, styles.circle, styles.border1PX, styles.borderColorBrand, styles.paddingHorizontal20PX, styles.paddingVertical5PX].join(' ')}>
|
<button className={[_s.default, _s.marginTop5PX, _s.colorBrand, _s.text, _s.cursorPointer, _s.fontSize14PX, _s.circle, _s.border1PX, _s.borderColorBrand, _s.paddingHorizontal20PX, _s.paddingVertical5PX].join(' ')}>
|
||||||
{intl.formatMessage(messages.follow)}
|
{intl.formatMessage(messages.follow)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={[styles.default, styles.marginLeftAuto].join(' ')}>
|
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
|
||||||
<button className={[styles.default, styles.circle, styles.backgroundTransparent, styles.paddingVertical5PX, styles.paddingHorizontal5PX, styles.cursorPointer].join(' ')}>
|
<button className={[_s.default, _s.circle, _s.backgroundTransparent, _s.paddingVertical5PX, _s.paddingHorizontal5PX, _s.cursorPointer].join(' ')}>
|
||||||
<Icon className={styles.fillColorSubtle} id='close' width='8px' height='8px' />
|
<Icon className={_s.fillcolorSecondary} id='close' width='8px' height='8px' />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -14,14 +14,14 @@ const GabLogo = ({
|
|||||||
xmlSpace='preserve'
|
xmlSpace='preserve'
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<path className={styles.fillColorBrand} d='M13.8,7.6h-2.4v0.7V9l-0.4-0.3C10.2,7.8,9,7.2,7.7,7.2c-0.2,0-0.4,0-0.4,0c-0.1,0-0.3,0-0.5,0
|
<path className={_s.fillColorBrand} d='M13.8,7.6h-2.4v0.7V9l-0.4-0.3C10.2,7.8,9,7.2,7.7,7.2c-0.2,0-0.4,0-0.4,0c-0.1,0-0.3,0-0.5,0
|
||||||
c-5.6,0.3-8.7,7.2-5.4,12.1c2.3,3.4,7.1,4.1,9.7,1.5l0.3-0.3l0,0.7c0,1-0.1,1.5-0.4,2.2c-1,2.4-4.1,3-6.8,1.3
|
c-5.6,0.3-8.7,7.2-5.4,12.1c2.3,3.4,7.1,4.1,9.7,1.5l0.3-0.3l0,0.7c0,1-0.1,1.5-0.4,2.2c-1,2.4-4.1,3-6.8,1.3
|
||||||
c-0.2-0.1-0.4-0.2-0.4-0.2c-0.1,0.1-1.9,3.5-1.9,3.6c0,0.1,0.5,0.4,0.8,0.6c2.2,1.4,5.6,1.7,8.3,0.8c2.7-0.9,4.5-3.2,5-6.4
|
c-0.2-0.1-0.4-0.2-0.4-0.2c-0.1,0.1-1.9,3.5-1.9,3.6c0,0.1,0.5,0.4,0.8,0.6c2.2,1.4,5.6,1.7,8.3,0.8c2.7-0.9,4.5-3.2,5-6.4
|
||||||
c0.2-1.1,0.2-0.8,0.2-8.4l0-7.1H13.8z M9.7,17.6c-2.2,1.2-4.9-0.4-4.9-2.9C4.8,12.6,7,11,9,11.6C11.8,12.4,12.3,16.1,9.7,17.6z'/>
|
c0.2-1.1,0.2-0.8,0.2-8.4l0-7.1H13.8z M9.7,17.6c-2.2,1.2-4.9-0.4-4.9-2.9C4.8,12.6,7,11,9,11.6C11.8,12.4,12.3,16.1,9.7,17.6z'/>
|
||||||
<path className={styles.fillColorBrand} d='M45.6,7.7c-2.4-1-5-0.6-6.7,1L38.6,9V4.5V0h-2.4h-2.4v11v11h2.4h2.4v-0.7v-0.7l0.3,0.3
|
<path className={_s.fillColorBrand} d='M45.6,7.7c-2.4-1-5-0.6-6.7,1L38.6,9V4.5V0h-2.4h-2.4v11v11h2.4h2.4v-0.7v-0.7l0.3,0.3
|
||||||
c2.4,2.4,6.9,1.9,9.3-0.9C51.5,15.9,50.1,9.6,45.6,7.7z M43.7,17.5c-2.1,1.4-5.1-0.2-5.1-2.8c0-2.1,1.9-3.7,3.9-3.3
|
c2.4,2.4,6.9,1.9,9.3-0.9C51.5,15.9,50.1,9.6,45.6,7.7z M43.7,17.5c-2.1,1.4-5.1-0.2-5.1-2.8c0-2.1,1.9-3.7,3.9-3.3
|
||||||
C45.4,12.1,46.2,15.8,43.7,17.5z'/>
|
C45.4,12.1,46.2,15.8,43.7,17.5z'/>
|
||||||
<path className={styles.fillColorBrand} d='M31.5,12.5c-0.7-3.7-3.1-5.5-7.1-5.3c-1.7,0.1-4,0.7-4.8,1.4l-0.1,0.1l0.7,1.7c0.4,0.9,0.7,1.7,0.7,1.7
|
<path className={_s.fillColorBrand} d='M31.5,12.5c-0.7-3.7-3.1-5.5-7.1-5.3c-1.7,0.1-4,0.7-4.8,1.4l-0.1,0.1l0.7,1.7c0.4,0.9,0.7,1.7,0.7,1.7
|
||||||
c0,0,0.1,0,0.2-0.1c2.7-1.4,5.4-0.9,5.6,1.1l0,0.2l-0.4-0.1c-3-0.8-6.3-0.1-7.7,1.6c-1.8,2.2-0.9,5.8,1.7,7.1
|
c0,0,0.1,0,0.2-0.1c2.7-1.4,5.4-0.9,5.6,1.1l0,0.2l-0.4-0.1c-3-0.8-6.3-0.1-7.7,1.6c-1.8,2.2-0.9,5.8,1.7,7.1
|
||||||
c2.1,1,4.6,0.6,6.1-0.8l0.2-0.2v0.6v0.6h2.4h2.4v-4C31.7,13.7,31.7,13.3,31.5,12.5z M26.9,16.4c-0.1,0.7-0.5,1.5-1,2
|
c2.1,1,4.6,0.6,6.1-0.8l0.2-0.2v0.6v0.6h2.4h2.4v-4C31.7,13.7,31.7,13.3,31.5,12.5z M26.9,16.4c-0.1,0.7-0.5,1.5-1,2
|
||||||
c-1.2,1.1-3,0.7-3.2-0.7c-0.2-1,0.6-1.7,2-1.8c0.1,0,0.2,0,0.2,0c0,0,0.2,0,0.4,0c0.5,0,1,0.1,1.4,0.2l0.3,0.1L26.9,16.4z'/>
|
c-1.2,1.1-3,0.7-3.2-0.7c-0.2-1,0.6-1.7,2-1.8c0.1,0,0.2,0,0.2,0c0,0,0.2,0,0.4,0c0.5,0,1,0.1,1.4,0.2l0.3,0.1L26.9,16.4z'/>
|
||||||
|
@ -199,11 +199,11 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
|||||||
if (textarea) {
|
if (textarea) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className={[styles.default].join(' ')}>
|
<div className={[_s.default].join(' ')}>
|
||||||
<div className={[styles.default, styles.marginLeft5PX].join(' ')}>
|
<div className={[_s.default, _s.marginLeft5PX].join(' ')}>
|
||||||
<Textarea
|
<Textarea
|
||||||
inputRef={this.setTextbox}
|
inputRef={this.setTextbox}
|
||||||
className={[styles.default, styles.backgroundWhite, styles.lineHeight125, styles.resizeNone, styles.paddingVertical15PX, styles.outlineFocusBrand, styles.fontSize16PX, styles.text, styles.displayBlock].join(' ')}
|
className={[_s.default, _s.backgroundWhite, _s.lineHeight125, _s.resizeNone, _s.paddingVertical15PX, _s.outlineFocusBrand, _s.fontSize16PX, _s.text, _s.displayBlock].join(' ')}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoFocus={autoFocus}
|
autoFocus={autoFocus}
|
||||||
|
57
app/javascript/gabsocial/components/avatar.js
Normal file
57
app/javascript/gabsocial/components/avatar.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import { Map as ImmutableMap } from 'immutable'
|
||||||
|
import { autoPlayGif } from '../initial_state'
|
||||||
|
import Image from './image'
|
||||||
|
|
||||||
|
export default class Avatar extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
|
size: PropTypes.number.isRequired,
|
||||||
|
animate: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
account: ImmutableMap(),
|
||||||
|
animate: autoPlayGif,
|
||||||
|
size: 40,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
sameImg: this.props.account.get('avatar') === this.props.account.get('avatar_static'),
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { account, size, animate } = this.props
|
||||||
|
const { hovering, sameImg } = this.state
|
||||||
|
|
||||||
|
const shouldAnimate = animate || !sameImg
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
className: [_s.default, _s.circle].join(' '),
|
||||||
|
onMouseEnter: shouldAnimate ? this.handleMouseEnter : undefined,
|
||||||
|
onMouseLeave: shouldAnimate ? this.handleMouseLeave : undefined,
|
||||||
|
src: account.get((hovering || animate) ? 'avatar' : 'avatar_static'),
|
||||||
|
alt: account.get('display_name'),
|
||||||
|
style: {
|
||||||
|
width: `${size}px`,
|
||||||
|
height: `${size}px`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Image {...options} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,63 +0,0 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
|
||||||
import { Map as ImmutableMap } from 'immutable'
|
|
||||||
import classNames from 'classnames'
|
|
||||||
import { autoPlayGif } from '../../initial_state'
|
|
||||||
|
|
||||||
export default class Avatar extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
size: PropTypes.number,
|
|
||||||
inline: PropTypes.bool,
|
|
||||||
animate: PropTypes.bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
account: ImmutableMap(),
|
|
||||||
animate: autoPlayGif,
|
|
||||||
inline: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovering: false,
|
|
||||||
sameImg: this.props.account.get('avatar') === this.props.account.get('avatar_static'),
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseEnter = () => {
|
|
||||||
if (this.props.animate || this.state.sameImg) return
|
|
||||||
|
|
||||||
this.setState({ hovering: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleMouseLeave = () => {
|
|
||||||
if (this.props.animate || this.state.sameImg) return
|
|
||||||
|
|
||||||
this.setState({ hovering: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { account, size, animate, inline } = this.props
|
|
||||||
const { hovering } = this.state
|
|
||||||
|
|
||||||
// : TODO : remove inline and change all avatars to be sized using css
|
|
||||||
const style = !size ? {} : {
|
|
||||||
width: `${size}px`,
|
|
||||||
height: `${size}px`,
|
|
||||||
}
|
|
||||||
|
|
||||||
const theSrc = account.get((hovering || animate) ? 'avatar' : 'avatar_static')
|
|
||||||
|
|
||||||
return (
|
|
||||||
<img
|
|
||||||
className={[styles.default, styles.circle].join(' ')}
|
|
||||||
onMouseEnter={this.handleMouseEnter}
|
|
||||||
onMouseLeave={this.handleMouseLeave}
|
|
||||||
style={style}
|
|
||||||
src={theSrc}
|
|
||||||
alt={account.get('display_name')}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,36 +0,0 @@
|
|||||||
.avatar,
|
|
||||||
.account__avatar {
|
|
||||||
// @include avatar-radius();
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
// background-color: $ui-base-color;
|
|
||||||
object-fit: cover;
|
|
||||||
background-size: cover;
|
|
||||||
|
|
||||||
// TEMPORARY - need a default size for the avatars for now
|
|
||||||
// They are sized individually all over the application and need to change with breakpoints as well
|
|
||||||
// Might create a mixin to accept the size attribute and apply the various properties where necessary
|
|
||||||
@include size(56px);
|
|
||||||
|
|
||||||
&--inline {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-composite {
|
|
||||||
// @include avatar-radius();
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&>div {
|
|
||||||
// @include avatar-radius();
|
|
||||||
float: left;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a .account__avatar {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './avatar';
|
|
@ -1,31 +0,0 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
|
||||||
import { autoPlayGif } from '../../initial_state';
|
|
||||||
|
|
||||||
export default class AvatarOverlay extends ImmutablePureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
|
||||||
friend: ImmutablePropTypes.map.isRequired,
|
|
||||||
animate: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
animate: autoPlayGif,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account, friend, animate } = this.props;
|
|
||||||
|
|
||||||
const baseSrc = account.get(animate ? 'avatar' : 'avatar_static');
|
|
||||||
const overlaySrc = friend.get(animate ? 'avatar' : 'avatar_static');
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='avatar-overlay'>
|
|
||||||
<img className='avatar-overlay__base' src={baseSrc} />
|
|
||||||
<img className='avatar-overlay__overlay' src={overlaySrc} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
.avatar-overlay {
|
|
||||||
background-size: 48px 48px;
|
|
||||||
|
|
||||||
@include circle(48px);
|
|
||||||
|
|
||||||
&__base {
|
|
||||||
@include circle(36px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__overlay {
|
|
||||||
@include circle(36px);
|
|
||||||
@include abs-position(auto, 0, 0);
|
|
||||||
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './avatar_overlay';
|
|
33
app/javascript/gabsocial/components/badge.js
Normal file
33
app/javascript/gabsocial/components/badge.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
export default class Badge extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.string,
|
||||||
|
popover: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, popover } = this.props
|
||||||
|
const { hovering } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Text color='secondary' size='small' className={_s.marginVertical5PX}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
94
app/javascript/gabsocial/components/button.js
Normal file
94
app/javascript/gabsocial/components/button.js
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import classNames from 'classnames/bind'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
const COLORS = {
|
||||||
|
primary: 'primary',
|
||||||
|
secondary: 'secondary',
|
||||||
|
tertiary: 'tertiary',
|
||||||
|
brand: 'brand',
|
||||||
|
error: 'error',
|
||||||
|
none: 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Button extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.node,
|
||||||
|
to: PropTypes.string,
|
||||||
|
href: PropTypes.string,
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
className: PropTypes.string,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
color: PropTypes.string,
|
||||||
|
block: PropTypes.bool,
|
||||||
|
text: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
outline: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
color: COLORS.brand,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClick = (e) => {
|
||||||
|
if (!this.props.disabled && this.props.onClick) {
|
||||||
|
this.props.onClick(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setRef = (c) => {
|
||||||
|
this.node = c
|
||||||
|
}
|
||||||
|
|
||||||
|
focus() {
|
||||||
|
this.node.focus()
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { block, className, disabled, text, to, children, href, outline, color } = this.props
|
||||||
|
|
||||||
|
// : todo :
|
||||||
|
const classes = cx(className, {
|
||||||
|
default: 1,
|
||||||
|
noUnderline: 1,
|
||||||
|
font: 1,
|
||||||
|
cursorPointer: 1,
|
||||||
|
textAlignCenter: 1,
|
||||||
|
|
||||||
|
backgroundColorBrand: !text && !outline,
|
||||||
|
|
||||||
|
// colorPrimary: 1,
|
||||||
|
// colorSecondary: 1,
|
||||||
|
colorWhite: [].indexOf(color) > -1,
|
||||||
|
colorBrand: text || [].indexOf(color) > -1,
|
||||||
|
|
||||||
|
// borderColorBrand: 1,
|
||||||
|
// border1PX: 1,
|
||||||
|
|
||||||
|
circle: !text,
|
||||||
|
|
||||||
|
paddingVertical10PX: !text,
|
||||||
|
paddingHorizontal15PX: !text,
|
||||||
|
|
||||||
|
width100PC: block,
|
||||||
|
})
|
||||||
|
|
||||||
|
const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button'
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
tagName,
|
||||||
|
{
|
||||||
|
className: classes,
|
||||||
|
ref: this.setRef,
|
||||||
|
disabled: disabled,
|
||||||
|
to: to || undefined,
|
||||||
|
to: href || undefined,
|
||||||
|
onClick: this.handleClick || undefined,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,79 +0,0 @@
|
|||||||
import { NavLink } from 'react-router-dom'
|
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
|
|
||||||
export default class Button extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
children: PropTypes.node,
|
|
||||||
text: PropTypes.node,
|
|
||||||
to: PropTypes.string,
|
|
||||||
href: PropTypes.string,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
disabled: PropTypes.bool,
|
|
||||||
block: PropTypes.bool,
|
|
||||||
secondary: PropTypes.bool,
|
|
||||||
className: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
handleClick = (e) => {
|
|
||||||
if (!this.props.disabled && this.props.onClick) {
|
|
||||||
this.props.onClick(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setRef = (c) => {
|
|
||||||
this.node = c
|
|
||||||
}
|
|
||||||
|
|
||||||
focus() {
|
|
||||||
this.node.focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { block, className, disabled, text, to, children, href } = this.props
|
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const classes = cx(className, {
|
|
||||||
default: 1,
|
|
||||||
noUnderline: 1,
|
|
||||||
font: 1,
|
|
||||||
colorWhite: 1,
|
|
||||||
circle: 1,
|
|
||||||
cursorPointer: 1,
|
|
||||||
textAlignCenter: 1,
|
|
||||||
paddingVertical10PX: 1,
|
|
||||||
paddingHorizontal15PX: 1,
|
|
||||||
width100PC: block,
|
|
||||||
backgroundColorBrand: 1,
|
|
||||||
backgroundColorBrandDark_onHover: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
if (href) {
|
|
||||||
return (
|
|
||||||
<a className={classes} href={href}>
|
|
||||||
{text || children}
|
|
||||||
</a>
|
|
||||||
)
|
|
||||||
} else if (to) {
|
|
||||||
return (
|
|
||||||
<NavLink className={classes} to={to}>
|
|
||||||
{text || children}
|
|
||||||
</NavLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
ref={this.setRef}
|
|
||||||
disabled={disabled}
|
|
||||||
onClick={this.handleClick}
|
|
||||||
className={classes}
|
|
||||||
>
|
|
||||||
{text || children}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,92 +0,0 @@
|
|||||||
.button {
|
|
||||||
display: inline-block;
|
|
||||||
font-family: inherit;
|
|
||||||
position: relative;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: $ui-highlight-color;
|
|
||||||
box-sizing: border-box;
|
|
||||||
color: $primary-text-color;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@include border-design(transparent, 10px, 4px);
|
|
||||||
@include text-sizing(14px, 600, 1em, center);
|
|
||||||
@include size(auto, 36px);
|
|
||||||
// @include text-overflow(nowrap);
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($ui-highlight-color, 10%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled,
|
|
||||||
&.disabled {
|
|
||||||
background-color: $ui-primary-color;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner {
|
|
||||||
border: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&::-moz-focus-inner,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
outline: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--block {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--destructive {
|
|
||||||
transition: none;
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
background-color: $error-red;
|
|
||||||
transition: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--alternative {
|
|
||||||
color: $inverted-text-color;
|
|
||||||
background: $ui-primary-color;
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($ui-primary-color, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--alternative-2 {
|
|
||||||
background: $ui-base-lighter-color;
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
background-color: lighten($ui-base-lighter-color, 4%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--secondary {
|
|
||||||
color: $darker-text-color;
|
|
||||||
background: transparent;
|
|
||||||
padding: 3px 15px;
|
|
||||||
border: 1px solid $ui-primary-color;
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:hover {
|
|
||||||
border-color: lighten($ui-primary-color, 4%);
|
|
||||||
color: lighten($darker-text-color, 4%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './button';
|
|
@ -1,6 +1,5 @@
|
|||||||
import { isMobile } from '../../utils/is_mobile';
|
import { isMobile } from '../../utils/is_mobile';
|
||||||
import { ColumnHeader } from '../column_header';
|
import { ColumnHeader } from '../column_header';
|
||||||
import ColumnBackButton from '../column_back_button';
|
|
||||||
|
|
||||||
export default class Column extends PureComponent {
|
export default class Column extends PureComponent {
|
||||||
|
|
||||||
@ -26,8 +25,7 @@ export default class Column extends PureComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div role='region' aria-labelledby={columnHeaderId} className={[styles.default].join(' ')}>
|
<div role='region' aria-labelledby={columnHeaderId} className={[_s.default].join(' ')}>
|
||||||
{ backBtn && <ColumnBackButton slim={backBtn === 'slim'} />}
|
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { FormattedMessage } from 'react-intl';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import Icon from '../icon';
|
|
||||||
|
|
||||||
export default class ColumnBackButton extends PureComponent {
|
|
||||||
|
|
||||||
static contextTypes = {
|
|
||||||
router: PropTypes.object,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
slim: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
handleClick = () => {
|
|
||||||
if (window.history && window.history.length === 1) {
|
|
||||||
this.context.router.history.push('/home'); // homehack
|
|
||||||
} else {
|
|
||||||
this.context.router.history.goBack();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { slim } = this.props;
|
|
||||||
|
|
||||||
const btnClasses = classNames('column-back-button', {
|
|
||||||
'column-back-button--slim': slim,
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button className={btnClasses} onClick={this.handleClick}>
|
|
||||||
<Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
|
|
||||||
<FormattedMessage id='column_back_button.label' defaultMessage='Back' />
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
.column-back-button {
|
|
||||||
background: lighten($ui-base-color, 4%);
|
|
||||||
color: $highlight-text-color;
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
border: 0;
|
|
||||||
text-align: unset;
|
|
||||||
padding: 15px;
|
|
||||||
margin: 0;
|
|
||||||
z-index: 3;
|
|
||||||
outline: 0;
|
|
||||||
|
|
||||||
@include text-sizing(16px, 400, 1);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--slim {
|
|
||||||
cursor: pointer;
|
|
||||||
flex: 0 0 auto;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 15px;
|
|
||||||
|
|
||||||
@include abs-position(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './column_back_button';
|
|
@ -19,6 +19,8 @@ class ColumnHeader extends PureComponent {
|
|||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
active: PropTypes.bool,
|
active: PropTypes.bool,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
|
showBackBtn: PropTypes.bool,
|
||||||
|
actions: PropTypes.array,
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -45,24 +47,38 @@ class ColumnHeader extends PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { title, icon, active, children, intl: { formatMessage } } = this.props
|
const { title, showBackBtn, icon, active, children, actions, intl: { formatMessage } } = this.props
|
||||||
const { collapsed } = this.state
|
const { collapsed } = this.state
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.height100PC, styles.flexRow].join(' ')}>
|
<div className={[_s.default, _s.height100PC, _s.flexRow].join(' ')}>
|
||||||
{ /* <button className={[styles.default, styles.cursorPointer, styles.backgroundTransparent, styles.alignItemsCenter, styles.marginRight10PX, styles.justifyContentCenter].join(' ')}>
|
{
|
||||||
<Icon className={[styles.marginRight5PX, styles.fillColorBrand].join(' ')} id='back' width='24px' height='24px' />
|
showBackBtn &&
|
||||||
</button> */ }
|
<button className={[_s.default, _s.cursorPointer, _s.backgroundTransparent, _s.alignItemsCenter, _s.marginRight10PX, _s.justifyContentCenter].join(' ')}>
|
||||||
<h1 role='heading' className={[styles.default, styles.height100PC, styles.justifyContentCenter].join(' ')}>
|
<Icon className={[_s.marginRight5PX, _s.fillColorBrand].join(' ')} id='back' width='20px' height='20px' />
|
||||||
<span className={[styles.default, styles.text, styles.fontSize24PX, styles.fontWeight500, styles.colorBlack].join(' ')}>
|
</button>
|
||||||
|
}
|
||||||
|
<h1 role='heading' className={[_s.default, _s.height100PC, _s.justifyContentCenter].join(' ')}>
|
||||||
|
<span className={[_s.default, _s.text, _s.fontSize24PX, _s.fontWeightMedium, _s.colorPrimary].join(' ')}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<div className={[styles.default, styles.backgroundTransparent, styles.flexRow, styles.alignItemsCenter, styles.justifyContentCenter, styles.marginLeftAuto].join(' ')}>
|
{
|
||||||
<button className={[styles.default, styles.marginLeft5PX, styles.cursorPointer, styles.backgroundSubtle2, styles.paddingHorizontal10PX, styles.paddingVertical5PX, styles.radiusSmall].join(' ')}>
|
!!actions &&
|
||||||
<Icon className={styles.fillColorSubtle} id='ellipsis' width='24px' height='24px' />
|
<div className={[_s.default, _s.backgroundTransparent, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.marginLeftAuto].join(' ')}>
|
||||||
</button>
|
{
|
||||||
</div>
|
actions.map((action, i) => (
|
||||||
|
<button
|
||||||
|
onClick={() => action.onClick()}
|
||||||
|
key={`column-header-action-btn-${i}`}
|
||||||
|
className={[_s.default, _s.marginLeft5PX, _s.cursorPointer, _s.backgroundSubtle2, _s.paddingHorizontal10PX, _s.paddingVertical10PX, _s.radiusSmall].join(' ')}
|
||||||
|
>
|
||||||
|
<Icon className={_s.fillcolorSecondary} id={action.icon} width='20px' height='20px' />
|
||||||
|
</button>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
import { Link } from 'react-router-dom';
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import Icon from '../icon';
|
|
||||||
|
|
||||||
export default class ColumnHeaderSettingButton extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
title: PropTypes.node.isRequired,
|
|
||||||
icon: PropTypes.string.isRequired,
|
|
||||||
onClick: PropTypes.func,
|
|
||||||
to: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { title, icon, to, onClick } = this.props;
|
|
||||||
|
|
||||||
const classes = classNames('column-header-setting-btn', {
|
|
||||||
'column-header-setting-btn--link': !!to
|
|
||||||
});
|
|
||||||
|
|
||||||
if (to) {
|
|
||||||
return (
|
|
||||||
<Link to={to} className={classes}>
|
|
||||||
{title}
|
|
||||||
<Icon id={icon} />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button className={classes} tabIndex='0' onClick={onClick}>
|
|
||||||
<Icon id={icon} />
|
|
||||||
{title}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,28 +0,0 @@
|
|||||||
.column-header-setting-btn {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
color: inherit;
|
|
||||||
border: 0;
|
|
||||||
background: transparent;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0 10px;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: $darker-text-color;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--link {
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
.fa {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './column_header_setting_button';
|
|
@ -28,11 +28,11 @@ class ColumnIndicator extends PureComponent {
|
|||||||
const title = type !== 'error' ? intl.formatMessage(messages[type]) : message
|
const title = type !== 'error' ? intl.formatMessage(messages[type]) : message
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.width100PC, styles.justifyContentCenter, styles.alignItemsCenter, styles.paddingVertical15PX].join(' ')}>
|
<div className={[_s.default, _s.width100PC, _s.justifyContentCenter, _s.alignItemsCenter, _s.paddingVertical15PX].join(' ')}>
|
||||||
<Icon id={type} width='52px' height='52px' />
|
<Icon id={type} width='52px' height='52px' />
|
||||||
{
|
{
|
||||||
type !== 'loading' &&
|
type !== 'loading' &&
|
||||||
<span className={[styles.default, styles.marginTop10PX, styles.text, styles.displayFlex, styles.colorBrand, styles.fontWeightNormal, styles.fontSize14PX].join(' ')}>
|
<span className={[_s.default, _s.marginTop10PX, _s.text, _s.displayFlex, _s.colorBrand, _s.fontWeightNormal, _s.fontSize14PX].join(' ')}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
import { Link } from 'react-router-dom';
|
|
||||||
import Icon from '../../components/icon';
|
|
||||||
|
|
||||||
export default class ColumnLink extends PureComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
icon: PropTypes.string.isRequired,
|
|
||||||
text: PropTypes.string.isRequired,
|
|
||||||
to: PropTypes.string,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { to, icon, text } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link to={to} className='column-link'>
|
|
||||||
<Icon id={icon} fixedWidth className='column-link__icon' />
|
|
||||||
{text}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,39 +0,0 @@
|
|||||||
.column-link {
|
|
||||||
background: lighten($ui-base-color, 8%);
|
|
||||||
color: $primary-text-color;
|
|
||||||
display: block;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 15px;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background: lighten($ui-base-color, 11%);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--transparent {
|
|
||||||
background: transparent;
|
|
||||||
color: $ui-secondary-color;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&:focus,
|
|
||||||
&:active {
|
|
||||||
background: transparent;
|
|
||||||
color: $primary-text-color;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.active {
|
|
||||||
color: $ui-highlight-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__icon {
|
|
||||||
display: inline-block;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './column_link';
|
|
@ -1,15 +0,0 @@
|
|||||||
export default class ColumnSubheading extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
text: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { text } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='column-subheading'>
|
|
||||||
{text}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,9 +0,0 @@
|
|||||||
.column-subheading {
|
|
||||||
background: $ui-base-color;
|
|
||||||
color: $dark-text-color;
|
|
||||||
padding: 8px 20px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
cursor: default;
|
|
||||||
|
|
||||||
@include text-sizing(12px, 500);
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './column_subheading';
|
|
@ -6,27 +6,28 @@ export default class DisplayName extends ImmutablePureComponent {
|
|||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
account: ImmutablePropTypes.map.isRequired,
|
account: ImmutablePropTypes.map.isRequired,
|
||||||
|
multiline: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account } = this.props;
|
const { account } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={[styles.default, styles.flexRow, styles.maxWidth100PC, styles.alignItemsCenter].join(' ')}>
|
<span className={[_s.default, _s.flexRow, _s.maxWidth100PC, _s.alignItemsCenter].join(' ')}>
|
||||||
<bdi className={[styles.text, styles.whiteSpaceNoWrap, styles.textOverflowEllipsis].join(' ')}>
|
<bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}>
|
||||||
<strong
|
<strong
|
||||||
className={[styles.text, styles.overflowWrapBreakWord, styles.whiteSpaceNoWrap, styles.fontSize15PX, styles.fontWeightBold, styles.colorBlack, styles.lineHeight125, styles.marginRight2PX].join(' ')}
|
className={[_s.text, _s.overflowWrapBreakWord, _s.whiteSpaceNoWrap, _s.fontSize15PX, _s.fontWeightBold, _s.colorPrimary, _s.lineHeight125, _s.marginRight2PX].join(' ')}
|
||||||
dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }}
|
dangerouslySetInnerHTML={{ __html: account.get('display_name_html') }}
|
||||||
/>
|
/>
|
||||||
</bdi>
|
</bdi>
|
||||||
{
|
{
|
||||||
account.get('is_verified') &&
|
account.get('is_verified') &&
|
||||||
<Icon id='verified' width='16px' height='16px' className={[styles.default]} title='Verified Account' />
|
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Verified Account' />
|
||||||
/*<Icon id='verified' width='15px' height='15px' className={[styles.default]} title='PRO' />
|
/*<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='PRO' />
|
||||||
<Icon id='verified' width='15px' height='15px' className={[styles.default]} title='Donor' />
|
<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='Donor' />
|
||||||
<Icon id='verified' width='15px' height='15px' className={[styles.default]} title='Investor' />*/
|
<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='Investor' />*/
|
||||||
}
|
}
|
||||||
<span className={[styles.text, styles.displayFlex, styles.flexNormal, styles.flexShrink1, styles.fontSize15PX, styles.overflowWrapBreakWord, styles.textOverflowEllipsis, styles.marginLeft5PX, styles.colorSubtle, styles.fontWeightNormal, styles.lineHeight125].join(' ')}>
|
<span className={[_s.text, _s.displayFlex, _s.flexNormal, _s.flexShrink1, _s.fontSize15PX, _s.overflowWrapBreakWord, _s.textOverflowEllipsis, _s.marginLeft5PX, _s.colorSecondary, _s.fontWeightNormal, _s.lineHeight125].join(' ')}>
|
||||||
@{account.get('acct')}
|
@{account.get('acct')}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
7
app/javascript/gabsocial/components/divider.js
Normal file
7
app/javascript/gabsocial/components/divider.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default class Divider extends PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={[_s.default, _s.borderBottom1PX, _s.bordercolorSecondary2, _s.marginBottom15PX, _s.width100PC].join(' ')} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
80
app/javascript/gabsocial/components/group_list_item.js
Normal file
80
app/javascript/gabsocial/components/group_list_item.js
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import { Fragment } from 'react'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
|
import { shortNumberFormat } from '../utils/numbers'
|
||||||
|
import Image from './image'
|
||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
new_statuses: { id: 'groups.sidebar-panel.item.view', defaultMessage: 'new gabs' },
|
||||||
|
no_recent_activity: { id: 'groups.sidebar-panel.item.no_recent_activity', defaultMessage: 'No recent activity' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = (state, { id }) => ({
|
||||||
|
group: state.getIn(['groups', id]),
|
||||||
|
relationships: state.getIn(['group_relationships', id]),
|
||||||
|
})
|
||||||
|
|
||||||
|
export default
|
||||||
|
@connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
|
class GroupListItem extends ImmutablePureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
group: ImmutablePropTypes.map,
|
||||||
|
relationships: ImmutablePropTypes.map,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { intl, group, relationships } = this.props
|
||||||
|
const { hovering } = this.state
|
||||||
|
|
||||||
|
if (!relationships) return null
|
||||||
|
|
||||||
|
const unreadCount = relationships.get('unread_count')
|
||||||
|
|
||||||
|
const subtitle = unreadCount > 0 ? (
|
||||||
|
<Fragment>
|
||||||
|
{shortNumberFormat(unreadCount)}
|
||||||
|
|
||||||
|
{intl.formatMessage(messages.new_statuses)}
|
||||||
|
</Fragment>
|
||||||
|
) : intl.formatMessage(messages.no_recent_activity)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={`/groups/${group.get('id')}`}
|
||||||
|
className={[_s.default, _s.noUnderline, _s.marginTop5PX, _s.overflowHidden, _s.radiusSmall, _s.marginBottom10PX, _s.border1PX, _s.bordercolorSecondary, _s.backgroundSubtle_onHover].join(' ')}
|
||||||
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={group.get('cover')}
|
||||||
|
alt={group.get('title')}
|
||||||
|
className={_s.height122PX}
|
||||||
|
/>
|
||||||
|
<div className={[_s.default, _s.paddingHorizontal10PX, _s.marginVertical5PX].join(' ')}>
|
||||||
|
<Text color='brand' weight='bold' underline={hovering}>
|
||||||
|
{group.get('title')}
|
||||||
|
</Text>
|
||||||
|
<Text color='secondary' size='small' className={_s.marginVertical5PX}>
|
||||||
|
{subtitle}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
47
app/javascript/gabsocial/components/hashtag_item.js
Normal file
47
app/javascript/gabsocial/components/hashtag_item.js
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { FormattedMessage } from 'react-intl'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import { shortNumberFormat } from '../utils/numbers'
|
||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
export default class HashtagItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
hashtag: ImmutablePropTypes.map.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { hashtag } = this.props
|
||||||
|
const { hovering } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to='/test'
|
||||||
|
className={[_s.default, _s.noUnderline, _s.backgroundSubtle_onHover, _s.paddingHorizontal15PX, _s.paddingVertical5PX].join(' ')}
|
||||||
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
|
>
|
||||||
|
<Text color='brand' size='medium' weight='bold' className={_s.paddingVertical2PX}>
|
||||||
|
#randomhashtag
|
||||||
|
</Text>
|
||||||
|
<Text color='secondary' size='small' underline={hovering} className={_s.paddingVertical2PX}>
|
||||||
|
10,240 Gabs
|
||||||
|
</Text>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
55
app/javascript/gabsocial/components/heading.js
Normal file
55
app/javascript/gabsocial/components/heading.js
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import classNames from 'classnames/bind'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
const SIZES = {
|
||||||
|
h1: 'h1',
|
||||||
|
h2: 'h2',
|
||||||
|
h3: 'h3',
|
||||||
|
h4: 'h4',
|
||||||
|
h5: 'h5',
|
||||||
|
h6: 'h6',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Heading extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
children: PropTypes.any,
|
||||||
|
size: PropTypes.oneOf(Object.keys(SIZES)),
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
size: SIZES.h1,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { className, children, size } = this.props
|
||||||
|
|
||||||
|
const classes = cx({
|
||||||
|
default: 1,
|
||||||
|
text: 1,
|
||||||
|
|
||||||
|
colorPrimary: [SIZES.h1, SIZES.h3].indexOf(size) > -1,
|
||||||
|
colorSecondary: [SIZES.h2, SIZES.h4].indexOf(size) > -1,
|
||||||
|
|
||||||
|
fontSize24PX: size === SIZES.h1,
|
||||||
|
fontSize19PX: size === SIZES.h2,
|
||||||
|
fontSize16PX: size === SIZES.h3,
|
||||||
|
fontSize13PX: size === SIZES.h4,
|
||||||
|
|
||||||
|
marginTop5PX: [SIZES.h2, SIZES.h4].indexOf(size) > -1,
|
||||||
|
|
||||||
|
// fontWeightNormal: weight === WEIGHTS.normal,
|
||||||
|
// fontWeightMedium: weight === WEIGHTS.medium,
|
||||||
|
fontWeightBold: [SIZES.h3, SIZES.h4].indexOf(size) > -1
|
||||||
|
})
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
size,
|
||||||
|
{
|
||||||
|
className: classes,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,6 @@
|
|||||||
|
import AddIcon from './svgs/add_icon'
|
||||||
import AppsIcon from './svgs/apps_icon'
|
import AppsIcon from './svgs/apps_icon'
|
||||||
|
import AngleRightIcon from './svgs/angle_right_icon'
|
||||||
import BackIcon from './svgs/back_icon'
|
import BackIcon from './svgs/back_icon'
|
||||||
import CalendarIcon from './svgs/calendar_icon'
|
import CalendarIcon from './svgs/calendar_icon'
|
||||||
import ChatIcon from './svgs/chat_icon'
|
import ChatIcon from './svgs/chat_icon'
|
||||||
@ -20,6 +22,7 @@ import RepostIcon from './svgs/repost_icon'
|
|||||||
import SearchIcon from './svgs/search_icon'
|
import SearchIcon from './svgs/search_icon'
|
||||||
import ShareIcon from './svgs/share_icon'
|
import ShareIcon from './svgs/share_icon'
|
||||||
import ShopIcon from './svgs/shop_icon'
|
import ShopIcon from './svgs/shop_icon'
|
||||||
|
import SubtractIcon from './svgs/subtract_icon'
|
||||||
import TrendsIcon from './svgs/trends_icon'
|
import TrendsIcon from './svgs/trends_icon'
|
||||||
import VerifiedIcon from './svgs/verified_icon'
|
import VerifiedIcon from './svgs/verified_icon'
|
||||||
import WarningIcon from './svgs/warning_icon'
|
import WarningIcon from './svgs/warning_icon'
|
||||||
@ -37,8 +40,12 @@ export default class Icon extends PureComponent {
|
|||||||
const { id, ...options } = this.props
|
const { id, ...options } = this.props
|
||||||
|
|
||||||
switch (id) {
|
switch (id) {
|
||||||
|
case 'add':
|
||||||
|
return <AddIcon {...options} />
|
||||||
case 'apps':
|
case 'apps':
|
||||||
return <AppsIcon {...options} />
|
return <AppsIcon {...options} />
|
||||||
|
case 'angle-right':
|
||||||
|
return <AngleRightIcon {...options} />
|
||||||
case 'back':
|
case 'back':
|
||||||
return <BackIcon {...options} />
|
return <BackIcon {...options} />
|
||||||
case 'calendar':
|
case 'calendar':
|
||||||
@ -81,6 +88,8 @@ export default class Icon extends PureComponent {
|
|||||||
return <ShareIcon {...options} />
|
return <ShareIcon {...options} />
|
||||||
case 'shop':
|
case 'shop':
|
||||||
return <ShopIcon {...options} />
|
return <ShopIcon {...options} />
|
||||||
|
case 'subtract':
|
||||||
|
return <SubtractIcon {...options} />
|
||||||
case 'trends':
|
case 'trends':
|
||||||
return <TrendsIcon {...options} />
|
return <TrendsIcon {...options} />
|
||||||
case 'verified':
|
case 'verified':
|
||||||
|
26
app/javascript/gabsocial/components/icon/svgs/add_icon.js
Normal file
26
app/javascript/gabsocial/components/icon/svgs/add_icon.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const AddIcon = ({
|
||||||
|
className = '',
|
||||||
|
width = '16px',
|
||||||
|
height = '16px',
|
||||||
|
viewBox = '0 0 64 64',
|
||||||
|
title = 'Add',
|
||||||
|
}) => (
|
||||||
|
<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 61.5 29.5 L 34.5 29.5 L 34.5 2.5 C 34.5 1.121094 33.378906 0 32 0 C 30.621094 0 29.5 1.121094 29.5 2.5 L 29.5 29.5 L 2.5 29.5 C 1.121094 29.5 0 30.621094 0 32 C 0 33.378906 1.121094 34.5 2.5 34.5 L 29.5 34.5 L 29.5 61.5 C 29.5 62.878906 30.621094 64 32 64 C 33.378906 64 34.5 62.878906 34.5 61.5 L 34.5 34.5 L 61.5 34.5 C 62.878906 34.5 64 33.378906 64 32 C 64 30.621094 62.878906 29.5 61.5 29.5 Z M 61.5 29.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default AddIcon
|
@ -0,0 +1,26 @@
|
|||||||
|
const AngleRightIcon = ({
|
||||||
|
className = '',
|
||||||
|
width = '16px',
|
||||||
|
height = '16px',
|
||||||
|
viewBox = '0 0 64 64',
|
||||||
|
title = '',
|
||||||
|
}) => (
|
||||||
|
<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 49.777344 29.503906 L 21.296875 1.023438 C 20.640625 0.363281 19.761719 0 18.824219 0 C 17.882812 0 17.003906 0.363281 16.347656 1.023438 L 14.25 3.121094 C 12.882812 4.484375 12.882812 6.707031 14.25 8.070312 L 38.164062 31.988281 L 14.222656 55.929688 C 13.5625 56.589844 13.199219 57.46875 13.199219 58.40625 C 13.199219 59.34375 13.5625 60.222656 14.222656 60.882812 L 16.320312 62.976562 C 16.980469 63.636719 17.859375 64 18.796875 64 C 19.734375 64 20.613281 63.636719 21.273438 62.976562 L 49.777344 34.472656 C 50.4375 33.808594 50.800781 32.925781 50.800781 31.988281 C 50.800781 31.046875 50.4375 30.164062 49.777344 29.503906 Z M 49.777344 29.503906" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default AngleRightIcon
|
@ -18,7 +18,7 @@ const BackIcon = ({
|
|||||||
aria-label={title}
|
aria-label={title}
|
||||||
>
|
>
|
||||||
<g>
|
<g>
|
||||||
<path d="M 19.570312 9.542969 C 20.417969 8.695312 21.75 8.695312 22.59375 9.542969 C 23.410156 10.359375 23.410156 11.71875 22.59375 12.535156 L 7.265625 27.867188 L 57.851562 27.867188 C 59.03125 27.867188 60 28.804688 60 29.984375 C 60 31.164062 59.03125 32.128906 57.851562 32.128906 L 7.265625 32.128906 L 22.59375 47.433594 C 23.410156 48.277344 23.410156 49.640625 22.59375 50.457031 C 21.75 51.300781 20.417969 51.300781 19.570312 50.457031 L 0.613281 31.496094 C -0.207031 30.679688 -0.207031 29.316406 0.613281 28.503906 Z M 19.570312 9.542969" />
|
<path d="M 56.394531 25.789062 C 56.148438 25.75 55.898438 25.730469 55.652344 25.734375 L 13.324219 25.734375 L 14.246094 25.304688 C 15.148438 24.878906 15.96875 24.296875 16.671875 23.585938 L 28.539062 11.71875 C 30.105469 10.226562 30.367188 7.824219 29.164062 6.027344 C 27.761719 4.117188 25.078125 3.699219 23.164062 5.101562 C 23.007812 5.214844 22.863281 5.339844 22.722656 5.472656 L 1.257812 26.9375 C -0.417969 28.609375 -0.417969 31.328125 1.253906 33.007812 C 1.257812 33.007812 1.257812 33.007812 1.257812 33.011719 L 22.722656 54.476562 C 24.402344 56.148438 27.121094 56.144531 28.796875 54.464844 C 28.925781 54.335938 29.050781 54.195312 29.164062 54.046875 C 30.367188 52.25 30.105469 49.851562 28.539062 48.359375 L 16.691406 36.464844 C 16.0625 35.835938 15.339844 35.308594 14.546875 34.898438 L 13.257812 34.320312 L 55.414062 34.320312 C 57.609375 34.402344 59.53125 32.867188 59.945312 30.714844 C 60.324219 28.371094 58.734375 26.167969 56.394531 25.789062 Z M 56.394531 25.789062" />
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,7 @@ const LoadingIcon = ({
|
|||||||
>
|
>
|
||||||
<g transform='translate(82,50)'>
|
<g transform='translate(82,50)'>
|
||||||
<g transform='rotate(0)'>
|
<g transform='rotate(0)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='1' transform='scale(1.03405 1.03405)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='1' transform='scale(1.03405 1.03405)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-1.0294117647058822s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-1.0294117647058822s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-1.0294117647058822s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-1.0294117647058822s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -23,7 +23,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(72.62741699796952,72.62741699796952)'>
|
<g transform='translate(72.62741699796952,72.62741699796952)'>
|
||||||
<g transform='rotate(45)'>
|
<g transform='rotate(45)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.875' transform='scale(1.09655 1.09655)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.875' transform='scale(1.09655 1.09655)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.8823529411764705s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.8823529411764705s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.8823529411764705s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.8823529411764705s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -31,7 +31,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(50,82)'>
|
<g transform='translate(50,82)'>
|
||||||
<g transform='rotate(90)'>
|
<g transform='rotate(90)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.75' transform='scale(1.15905 1.15905)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.75' transform='scale(1.15905 1.15905)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.7352941176470588s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.7352941176470588s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.7352941176470588s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.7352941176470588s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -39,7 +39,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(27.37258300203048,72.62741699796952)'>
|
<g transform='translate(27.37258300203048,72.62741699796952)'>
|
||||||
<g transform='rotate(135)'>
|
<g transform='rotate(135)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.625' transform='scale(1.22155 1.22155)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.625' transform='scale(1.22155 1.22155)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.588235294117647s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.588235294117647s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.588235294117647s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.588235294117647s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -47,7 +47,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(18,50)'>
|
<g transform='translate(18,50)'>
|
||||||
<g transform='rotate(180)'>
|
<g transform='rotate(180)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.5' transform='scale(1.28405 1.28405)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.5' transform='scale(1.28405 1.28405)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.4411764705882352s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.4411764705882352s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.4411764705882352s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.4411764705882352s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -55,7 +55,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(27.372583002030474,27.37258300203048)'>
|
<g transform='translate(27.372583002030474,27.37258300203048)'>
|
||||||
<g transform='rotate(225)'>
|
<g transform='rotate(225)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.375' transform='scale(1.34655 1.34655)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.375' transform='scale(1.34655 1.34655)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.2941176470588235s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.2941176470588235s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.2941176470588235s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.2941176470588235s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -63,7 +63,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(50,18)'>
|
<g transform='translate(50,18)'>
|
||||||
<g transform='rotate(270)'>
|
<g transform='rotate(270)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.25' transform='scale(1.40905 1.40905)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.25' transform='scale(1.40905 1.40905)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='-0.14705882352941174s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='-0.14705882352941174s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.14705882352941174s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='-0.14705882352941174s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
@ -71,7 +71,7 @@ const LoadingIcon = ({
|
|||||||
</g>
|
</g>
|
||||||
<g transform='translate(72.62741699796952,27.372583002030474)'>
|
<g transform='translate(72.62741699796952,27.372583002030474)'>
|
||||||
<g transform='rotate(315)'>
|
<g transform='rotate(315)'>
|
||||||
<circle cx='0' cy='0' r='6' fill='#30ce7d' fill-opacity='0.125' transform='scale(1.47155 1.47155)'>
|
<circle cx='0' cy='0' r='6' fill='#30ce7d' fillOpacity='0.125' transform='scale(1.47155 1.47155)'>
|
||||||
<animateTransform attributeName='transform' type='scale' begin='0s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
<animateTransform attributeName='transform' type='scale' begin='0s' values='1.5 1.5;1 1' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite'></animateTransform>
|
||||||
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='0s'></animate>
|
<animate attributeName='fill-opacity' keyTimes='0;1' dur='1.176470588235294s' repeatCount='indefinite' values='1;0' begin='0s'></animate>
|
||||||
</circle>
|
</circle>
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
const SubtractIcon = ({
|
||||||
|
className = '',
|
||||||
|
width = '16px',
|
||||||
|
height = '16px',
|
||||||
|
viewBox = '0 0 64 64',
|
||||||
|
title = 'Subtract',
|
||||||
|
}) => (
|
||||||
|
<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 61.5 29.5 L 2.5 29.5 C 1.121094 29.5 0 30.621094 0 32 C 0 33.378906 1.121094 34.5 2.5 34.5 L 61.5 34.5 C 62.878906 34.5 64 33.378906 64 32 C 64 30.621094 62.878906 29.5 61.5 29.5 Z M 61.5 29.5" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
|
||||||
|
export default SubtractIcon
|
41
app/javascript/gabsocial/components/image.js
Normal file
41
app/javascript/gabsocial/components/image.js
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import classNames from 'classnames/bind'
|
||||||
|
import { autoPlayGif } from '../initial_state'
|
||||||
|
|
||||||
|
// : testing :
|
||||||
|
const placeholderSource = 'https://via.placeholder.com/150'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
export default class Image extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
alt: PropTypes.string,
|
||||||
|
src: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||||
|
fit: PropTypes.oneOf(['contain', 'cover', 'tile', 'none']),
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
width: '100%',
|
||||||
|
fit: 'cover',
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { src, fit, className, ...otherProps } = this.props
|
||||||
|
|
||||||
|
const source = src || placeholderSource
|
||||||
|
const classes = cx(className, {
|
||||||
|
default: 1,
|
||||||
|
objectFitCover: fit === 'cover'
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<img
|
||||||
|
className={classes}
|
||||||
|
{...otherProps}
|
||||||
|
src={source}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
9
app/javascript/gabsocial/components/input.js
Normal file
9
app/javascript/gabsocial/components/input.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default class Input extends PureComponent {
|
||||||
|
render() {
|
||||||
|
const { children } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<input />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,48 +1,52 @@
|
|||||||
|
import Sticky from 'react-stickynode'
|
||||||
import Search from '../search'
|
import Search from '../search'
|
||||||
import ColumnHeader from '../column_header'
|
import ColumnHeader from '../column_header'
|
||||||
import Header from '../header'
|
import Sidebar from '../sidebar'
|
||||||
|
|
||||||
export default class DefaultLayout extends PureComponent {
|
export default class DefaultLayout extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
actions: PropTypes.array,
|
||||||
layout: PropTypes.object,
|
layout: PropTypes.object,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
showBackBtn: PropTypes.boolean,
|
showBackBtn: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children, title, showBackBtn, layout } = this.props
|
const { children, title, showBackBtn, layout, actions } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.flexRow, styles.width100PC, styles.backgroundColorSubtle3].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
|
||||||
|
|
||||||
<Header />
|
<Sidebar />
|
||||||
|
|
||||||
<main role='main' className={[styles.default, styles.flexShrink1, styles.flexGrow1, styles.borderColorSubtle2, styles.borderLeft1PX].join(' ')}>
|
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.bordercolorSecondary2, _s.borderLeft1PX].join(' ')}>
|
||||||
|
|
||||||
<div className={[styles.default, styles.height53PX, styles.borderBottom1PX, styles.borderColorSubtle2, styles.backgroundColorSubtle3, styles.z3, styles.top0, styles.positionFixed].join(' ')}>
|
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.bordercolorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
|
||||||
<div className={[styles.default, styles.height53PX, styles.paddingLeft15PX, styles.width1015PX, styles.flexRow, styles.justifyContentSpaceBetween].join(' ')}>
|
<div className={[_s.default, _s.height53PX, _s.paddingLeft15PX, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
|
||||||
<div className={[styles.default, styles.width660PX].join(' ')}>
|
<div className={[_s.default, _s.width660PX].join(' ')}>
|
||||||
<ColumnHeader title={title} />
|
<ColumnHeader title={title} showBackBtn={showBackBtn} actions={actions} />
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.default, styles.width325PX].join(' ')}>
|
<div className={[_s.default, _s.width325PX].join(' ')}>
|
||||||
<Search />
|
<Search />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={[styles.default, styles.height53PX].join(' ')}></div>
|
<div className={[_s.default, _s.height53PX].join(' ')}></div>
|
||||||
|
|
||||||
<div className={[styles.default, styles.width1015PX, styles.flexRow, styles.justifyContentSpaceBetween, styles.paddingLeft15PX, styles.paddingVertical15PX].join(' ')}>
|
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.paddingLeft15PX, _s.paddingVertical15PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.width660PX, styles.z1].join(' ')}>
|
<div className={[_s.default, _s.width660PX, _s.z1].join(' ')}>
|
||||||
<div className={styles.default}>
|
<div className={_s.default}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={[styles.default, styles.width325PX].join(' ')}>
|
<div className={[_s.default, _s.width325PX].join(' ')}>
|
||||||
<div className={styles.default}>
|
<Sticky top={73} enabled>
|
||||||
{layout}
|
<div className={[_s.default, _s.width325PX].join(' ')}>
|
||||||
</div>
|
{layout}
|
||||||
|
</div>
|
||||||
|
</Sticky>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,32 +1,32 @@
|
|||||||
import ColumnHeader from '../column_header'
|
import ColumnHeader from '../column_header'
|
||||||
import Header from '../header'
|
import Sidebar from '../sidebar'
|
||||||
|
|
||||||
export default class ProfileLayout extends PureComponent {
|
export default class ProfileLayout extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
layout: PropTypes.object,
|
layout: PropTypes.object,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
showBackBtn: PropTypes.boolean,
|
showBackBtn: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children } = this.props
|
const { children } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.flexRow, styles.width100PC, styles.backgroundColorSubtle3].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.heightMin100VH, _s.backgroundcolorSecondary3].join(' ')}>
|
||||||
|
|
||||||
<Header />
|
<Sidebar />
|
||||||
|
|
||||||
<main role='main' className={[styles.default, styles.flexShrink1, styles.flexGrow1, styles.borderColorSubtle2, styles.borderLeft1PX].join(' ')}>
|
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.bordercolorSecondary2, _s.borderLeft1PX].join(' ')}>
|
||||||
|
|
||||||
<div className={[styles.default, styles.height350PX, styles.width100PC].join(' ')}>
|
<div className={[_s.default, _s.height350PX, _s.width100PC].join(' ')}>
|
||||||
<img
|
<img
|
||||||
className={[styles.default, styles.height350PX, styles.width100PC, styles.objectFitCover].join(' ')}
|
className={[_s.default, _s.height350PX, _s.width100PC, _s.objectFitCover].join(' ')}
|
||||||
src='https://gab.com/media/user/bz-5cf53d08403d4.jpeg'
|
src='https://gab.com/media/user/bz-5cf53d08403d4.jpeg'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={[styles.default, styles.width1015PX, styles.flexRow, styles.justifyContentSpaceBetween, styles.paddingLeft15PX, styles.paddingVertical15PX].join(' ')}>
|
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.paddingLeft15PX, _s.paddingVertical15PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.z1].join(' ')}>
|
<div className={[_s.default, _s.z1].join(' ')}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,6 +31,9 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
const currentYear = moment().format('YYYY')
|
||||||
|
|
||||||
export default @connect(null, mapDispatchToProps)
|
export default @connect(null, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class LinkFooter extends PureComponent {
|
class LinkFooter extends PureComponent {
|
||||||
@ -60,10 +63,6 @@ class LinkFooter extends PureComponent {
|
|||||||
const { onOpenHotkeys, intl } = this.props
|
const { onOpenHotkeys, intl } = this.props
|
||||||
const { hoveringItemIndex } = this.state
|
const { hoveringItemIndex } = this.state
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const currentYear = moment().format('YYYY')
|
|
||||||
|
|
||||||
const linkFooterItems = [
|
const linkFooterItems = [
|
||||||
{
|
{
|
||||||
to: '#',
|
to: '#',
|
||||||
@ -109,8 +108,8 @@ class LinkFooter extends PureComponent {
|
|||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.paddingHorizontal10PX].join(' ')}>
|
<div className={[_s.default, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<nav aria-label='Footer' role='navigation' className={[styles.default, styles.flexWrap, styles.flexRow].join(' ')}>
|
<nav aria-label='Footer' role='navigation' className={[_s.default, _s.flexWrap, _s.flexRow].join(' ')}>
|
||||||
{
|
{
|
||||||
linkFooterItems.map((linkFooterItem, i) => {
|
linkFooterItems.map((linkFooterItem, i) => {
|
||||||
if (linkFooterItem.requiresUser && !me) return null
|
if (linkFooterItem.requiresUser && !me) return null
|
||||||
@ -122,9 +121,9 @@ class LinkFooter extends PureComponent {
|
|||||||
paddingRight15PX: 1,
|
paddingRight15PX: 1,
|
||||||
cursorPointer: 1,
|
cursorPointer: 1,
|
||||||
backgroundTransparent: 1,
|
backgroundTransparent: 1,
|
||||||
colorSubtle: i !== hoveringItemIndex,
|
colorSecondary: i !== hoveringItemIndex,
|
||||||
noUnderline: i !== hoveringItemIndex,
|
noUnderline: i !== hoveringItemIndex,
|
||||||
colorBlack: i === hoveringItemIndex,
|
colorPrimary: i === hoveringItemIndex,
|
||||||
underline: i === hoveringItemIndex,
|
underline: i === hoveringItemIndex,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -157,14 +156,14 @@ class LinkFooter extends PureComponent {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
<span className={[styles.default, styles.text, styles.fontSize13PX, styles.colorSubtle, styles.marginVertical5PX].join(' ')}>© {currentYear} Gab AI, Inc.</span>
|
<span className={[_s.default, _s.text, _s.fontSize13PX, _s.colorSecondary, _s.marginVertical5PX].join(' ')}>© {currentYear} Gab AI, Inc.</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<p className={[styles.default, styles.text, styles.fontSize13PX, styles.colorSubtle, styles.marginTop10PX].join(' ')}>
|
<p className={[_s.default, _s.text, _s.fontSize13PX, _s.colorSecondary, _s.marginTop10PX, _s.marginBottom15PX].join(' ')}>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id='getting_started.open_source_notice'
|
id='getting_started.open_source_notice'
|
||||||
defaultMessage='Gab Social is open source software. You can contribute or report issues on our self-hosted GitLab at {gitlab}.'
|
defaultMessage='Gab Social is open source software. You can contribute or report issues on our self-hosted GitLab at {gitlab}.'
|
||||||
values={{ gitlab: <a href={source_url} className={[styles.inherit].join(' ')} rel='noopener' target='_blank'>{repository}</a> }}
|
values={{ gitlab: <a href={source_url} className={[_s.inherit].join(' ')} rel='noopener' target='_blank'>{repository}</a> }}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -31,7 +31,7 @@ class LoadMore extends PureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
className={[styles.default].join(' ')}
|
className={[_s.default].join(' ')}
|
||||||
disabled={disabled || !visible}
|
disabled={disabled || !visible}
|
||||||
style={{ visibility: visible ? 'visible' : 'hidden' }}
|
style={{ visibility: visible ? 'visible' : 'hidden' }}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
|
@ -15,6 +15,7 @@ const messages = defineMessages({
|
|||||||
hidden: { id: 'status.media_hidden', defaultMessage: 'Media hidden' },
|
hidden: { id: 'status.media_hidden', defaultMessage: 'Media hidden' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
class Item extends ImmutablePureComponent {
|
class Item extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
@ -139,7 +140,7 @@ class Item extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (attachment.get('type') === 'unknown') {
|
if (attachment.get('type') === 'unknown') {
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
<div className={[_s.default].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
||||||
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}>
|
<a className='media-gallery__item-thumbnail' href={attachment.get('remote_url')} target='_blank' style={{ cursor: 'pointer' }}>
|
||||||
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
<canvas width={32} height={32} ref={this.setCanvasRef} className='media-gallery__preview' />
|
||||||
</a>
|
</a>
|
||||||
@ -164,7 +165,7 @@ class Item extends ImmutablePureComponent {
|
|||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<a
|
<a
|
||||||
className={[styles.default, styles.overflowHidden, styles.height100PC, styles.width100PC, styles.cursorPointer].join(' ')}
|
className={[_s.default, _s.overflowHidden, _s.height100PC, _s.width100PC, _s.cursorPointer].join(' ')}
|
||||||
href={attachment.get('remote_url') || originalUrl}
|
href={attachment.get('remote_url') || originalUrl}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
target='_blank'
|
target='_blank'
|
||||||
@ -173,7 +174,7 @@ class Item extends ImmutablePureComponent {
|
|||||||
src={previewUrl}
|
src={previewUrl}
|
||||||
srcSet={srcSet}
|
srcSet={srcSet}
|
||||||
sizes={sizes}
|
sizes={sizes}
|
||||||
className={[styles.height100PC, styles.width100PC, styles.objectFitCover].join(' ')}
|
className={[_s.height100PC, _s.width100PC, _s.objectFitCover].join(' ')}
|
||||||
alt={attachment.get('description')}
|
alt={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
style={{ objectPosition: `${x}% ${y}%` }}
|
style={{ objectPosition: `${x}% ${y}%` }}
|
||||||
@ -185,9 +186,9 @@ class Item extends ImmutablePureComponent {
|
|||||||
const autoPlay = !isIOS() && autoPlayGif !== false;
|
const autoPlay = !isIOS() && autoPlayGif !== false;
|
||||||
|
|
||||||
thumbnail = (
|
thumbnail = (
|
||||||
<div className={[styles.default, styles.overflowHidden, styles.heigh100PC, styles.width100PC].join(' ')}>
|
<div className={[_s.default, _s.overflowHidden, _s.heigh100PC, _s.width100PC].join(' ')}>
|
||||||
<video
|
<video
|
||||||
className={[styles.default, styles.cursorPointer, styles.objectFitCover, styles.width100PC, styles.height100PC, styles.z1].join(' ')}
|
className={[_s.default, _s.cursorPointer, _s.objectFitCover, _s.width100PC, _s.height100PC, _s.z1].join(' ')}
|
||||||
aria-label={attachment.get('description')}
|
aria-label={attachment.get('description')}
|
||||||
title={attachment.get('description')}
|
title={attachment.get('description')}
|
||||||
role='application'
|
role='application'
|
||||||
@ -209,8 +210,8 @@ class Item extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.defeault, styles.positionAbsolute].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
<div className={[_s.defeault, _s.positionAbsolute].join(' ')} key={attachment.get('id')} style={{ position, float, left, top, right, bottom, height, width: `${width}%` }}>
|
||||||
<canvas width={0} height={0} ref={this.setCanvasRef} className={styles.displayNone} />
|
<canvas width={0} height={0} ref={this.setCanvasRef} className={_s.displayNone} />
|
||||||
{visible && thumbnail}
|
{visible && thumbnail}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -512,12 +513,11 @@ class MediaGallery extends PureComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
const containerClasses = cx({
|
const containerClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
displayBlock: 1,
|
displayBlock: 1,
|
||||||
overflowHidden: 1,
|
overflowHidden: 1,
|
||||||
borderColorSubtle: size === 1,
|
bordercolorSecondary: size === 1,
|
||||||
borderTop1PX: size === 1,
|
borderTop1PX: size === 1,
|
||||||
borderBottom1PX: size === 1,
|
borderBottom1PX: size === 1,
|
||||||
paddingHorizontal5PX: size > 1,
|
paddingHorizontal5PX: size > 1,
|
||||||
|
@ -1 +0,0 @@
|
|||||||
export { default } from './notification_counter';
|
|
@ -1,21 +0,0 @@
|
|||||||
.notification-counter {
|
|
||||||
box-sizing: border-box;
|
|
||||||
min-width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
padding: 1px 3px 0;
|
|
||||||
border-radius: 8px;
|
|
||||||
// @include font-montserrat();
|
|
||||||
color: #fff;
|
|
||||||
background: $gab-alert-red;
|
|
||||||
|
|
||||||
@include text-sizing(14px, 400, 14px, center);
|
|
||||||
@include abs-position(-14px, auto, auto, -16px);
|
|
||||||
|
|
||||||
@media screen and (max-width: $nav-breakpoint-1) {
|
|
||||||
@include abs-position(0, auto, auto, 27px, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-link--transparent .notification-counter {
|
|
||||||
border-color: darken($ui-base-color, 8%);
|
|
||||||
}
|
|
@ -1,19 +1,14 @@
|
|||||||
import { Fragment } from 'react'
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { defineMessages, injectIntl } from 'react-intl'
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import { NavLink } from 'react-router-dom'
|
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import { shortNumberFormat } from '../../utils/numbers'
|
|
||||||
import PanelLayout from './panel_layout'
|
import PanelLayout from './panel_layout'
|
||||||
import Icon from '../icon'
|
import GroupListItem from '../group_list_item'
|
||||||
|
import Button from '../button'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'groups.sidebar-panel.title', defaultMessage: 'Groups you\'re in' },
|
title: { id: 'groups.sidebar-panel.title', defaultMessage: 'Groups you\'re in' },
|
||||||
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
|
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
|
||||||
all: { id: 'groups.sidebar-panel.all', defaultMessage: 'All' },
|
all: { id: 'groups.sidebar-panel.all', defaultMessage: 'All' },
|
||||||
new_statuses: { id: 'groups.sidebar-panel.item.view', defaultMessage: 'new gabs' },
|
|
||||||
no_recent_activity: { id: 'groups.sidebar-panel.item.no_recent_activity', defaultMessage: 'No recent activity' },
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = (state) => ({
|
||||||
@ -36,119 +31,26 @@ class GroupSidebarPanel extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<PanelLayout
|
<PanelLayout
|
||||||
title={intl.formatMessage(messages.title)}
|
title={intl.formatMessage(messages.title)}
|
||||||
button={(
|
buttonTitle={intl.formatMessage(messages.all)}
|
||||||
<NavLink
|
buttonTo='/groups/browse/member'
|
||||||
to='/groups/browse/member'
|
|
||||||
className={[styles.default, styles.fontWeightBold, styles.text, styles.colorBrand, styles.fontSize13PX, styles.noUnderline].join(' ')}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.all)}
|
|
||||||
</NavLink>
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<div className={styles.default}>
|
<div className={_s.default}>
|
||||||
{
|
{
|
||||||
groupIds.slice(0, 6).map(groupId => (
|
groupIds.slice(0, 6).map(groupId => (
|
||||||
<GroupPanelItem key={`group-panel-item-${groupId}`} id={groupId} />
|
<GroupListItem
|
||||||
|
key={`group-panel-item-${groupId}`}
|
||||||
|
id={groupId}
|
||||||
|
/>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
count > 6 &&
|
count > 6 &&
|
||||||
<NavLink
|
<Button to='/groups/browse/member' block text>
|
||||||
to='/groups/browse/member'
|
|
||||||
className={[styles.default, styles.noUnderline, styles.paddingVertical5PX, styles.marginRightAuto, styles.marginTop5PX, styles.marginLeftAuto, styles.cursorPointer, styles.circle, styles.text, styles.displayFlex, styles.colorBrand].join(' ')}
|
|
||||||
>
|
|
||||||
{intl.formatMessage(messages.show_all)}
|
{intl.formatMessage(messages.show_all)}
|
||||||
</NavLink>
|
</Button>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps2 = (state, { id }) => ({
|
|
||||||
group: state.getIn(['groups', id]),
|
|
||||||
relationships: state.getIn(['group_relationships', id]),
|
|
||||||
})
|
|
||||||
|
|
||||||
@connect(mapStateToProps2)
|
|
||||||
@injectIntl
|
|
||||||
class GroupPanelItem extends ImmutablePureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
group: ImmutablePropTypes.map,
|
|
||||||
relationships: ImmutablePropTypes.map,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovering: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseEnter = () => {
|
|
||||||
this.setState({ hovering: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseLeave = () => {
|
|
||||||
this.setState({ hovering: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { intl, group, relationships } = this.props
|
|
||||||
const { hovering } = this.state
|
|
||||||
|
|
||||||
if (!relationships) return null
|
|
||||||
|
|
||||||
const unreadCount = relationships.get('unread_count')
|
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const titleClasses = cx({
|
|
||||||
default: 1,
|
|
||||||
text: 1,
|
|
||||||
displayFlex: 1,
|
|
||||||
colorBrand: 1,
|
|
||||||
fontSize14PX: 1,
|
|
||||||
fontWeightBold: 1,
|
|
||||||
lineHeight15: 1,
|
|
||||||
underline: hovering,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavLink
|
|
||||||
to={`/groups/${group.get('id')}`}
|
|
||||||
className={[styles.default, styles.noUnderline, styles.marginTop5PX, styles.overflowHidden, styles.radiusSmall, styles.marginBottom10PX, styles.border1PX, styles.borderColorSubtle].join(' ')}
|
|
||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
|
||||||
>
|
|
||||||
<div className={[styles.default, styles.height122PX].join(' ')}>
|
|
||||||
<img
|
|
||||||
src='https://gab.com/media/user/bz-5cf53d08403d4.jpeg'
|
|
||||||
alt={group.get('title')}
|
|
||||||
className={[styles.default, styles.objectFitCover, styles.height122PX, styles.width100PC].join(' ')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className={[styles.default, styles.paddingHorizontal10PX, styles.marginVertical5PX].join(' ')}>
|
|
||||||
<span className={titleClasses}>
|
|
||||||
{group.get('title')}
|
|
||||||
</span>
|
|
||||||
<span className={[styles.default, styles.text, styles.alignItemsCenter, styles.displayFlex, styles.flexRow, styles.colorSubtle, styles.fontSize13PX, styles.fontWeightNormal, styles.lineHeight15].join(' ')}>
|
|
||||||
{
|
|
||||||
unreadCount > 0 &&
|
|
||||||
<Fragment>
|
|
||||||
{shortNumberFormat(unreadCount)}
|
|
||||||
|
|
||||||
{intl.formatMessage(messages.new_statuses)}
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
unreadCount === 0 &&
|
|
||||||
<Fragment>
|
|
||||||
{intl.formatMessage(messages.no_recent_activity)}
|
|
||||||
</Fragment>
|
|
||||||
}
|
|
||||||
<span className={[styles.marginLeftAuto, styles.inherit].join(' ')}>→</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</NavLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
64
app/javascript/gabsocial/components/panel/hashtags_panel.js
Normal file
64
app/javascript/gabsocial/components/panel/hashtags_panel.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import { fetchHashtags } from '../../actions/hashtags'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import PanelLayout from './panel_layout'
|
||||||
|
import HashtagItem from '../hashtag_item'
|
||||||
|
import Button from '../button'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'hashtags.title', defaultMessage: 'Popular Hashtags' },
|
||||||
|
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
hashtags: state.getIn(['hashtags', 'items']),
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
fetchHashtags: () => dispatch(fetchHashtags()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class HashtagsPanel extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
hashtags: ImmutablePropTypes.list.isRequired,
|
||||||
|
fetchHashtags: PropTypes.func.isRequired,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.fetchHashtags()
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { intl, hashtags } = this.props
|
||||||
|
|
||||||
|
// !!! TESTING !!!
|
||||||
|
// if (hashtags.isEmpty()) {
|
||||||
|
// return null
|
||||||
|
// }
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelLayout title={intl.formatMessage(messages.title)} noPadding>
|
||||||
|
<div className={_s.default}>
|
||||||
|
{ /* hashtags && hashtags.map(hashtag => (
|
||||||
|
<HashtagingItem key={hashtag.get('name')} hashtag={hashtag} />
|
||||||
|
)) */ }
|
||||||
|
<HashtagItem />
|
||||||
|
<HashtagItem />
|
||||||
|
<HashtagItem />
|
||||||
|
<HashtagItem />
|
||||||
|
<HashtagItem />
|
||||||
|
</div>
|
||||||
|
<Button to='/groups/browse/member' block text>
|
||||||
|
{intl.formatMessage(messages.show_all)}
|
||||||
|
</Button>
|
||||||
|
</PanelLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
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 PanelLayout from './panel_layout';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
||||||
|
title: { id: 'lists.panel_title', defaultMessage: 'On This List ({count})' },
|
||||||
|
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = state => ({
|
||||||
|
// accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class ListDetailsPanel extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleShowAllLists() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { intl } = this.props;
|
||||||
|
|
||||||
|
const count = 10
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelLayout
|
||||||
|
title={intl.formatMessage(messages.title, { count })}
|
||||||
|
buttonTitle={intl.formatMessage(messages.show_all)}
|
||||||
|
buttonAction={this.handleShowAllLists}
|
||||||
|
>
|
||||||
|
<div className={_s.default}>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</PanelLayout>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
@ -1,37 +1,62 @@
|
|||||||
import classNames from 'classnames/bind'
|
import Heading from '../heading'
|
||||||
|
import Button from '../button'
|
||||||
|
|
||||||
export default class PanelLayout extends PureComponent {
|
export default class PanelLayout extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
hasBackground: PropTypes.boolean,
|
buttonTitle: PropTypes.string,
|
||||||
button: PropTypes.node,
|
buttonAction: PropTypes.func,
|
||||||
|
buttonTo: PropTypes.func,
|
||||||
|
noPadding: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { title, subtitle, button, hasBackground, children } = this.props
|
const { title, subtitle, buttonTitle, buttonAction, buttonTo, noPadding, children } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className={[styles.default, styles.backgroundWhite, styles.overflowHidden, styles.radiusSmall, styles.marginBottom15PX, styles.borderColorSubtle, styles.border1PX].join(' ')}>
|
<aside className={[_s.default, _s.backgroundWhite, _s.overflowHidden, _s.radiusSmall, _s.marginBottom15PX, _s.bordercolorSecondary, _s.border1PX].join(' ')}>
|
||||||
{
|
{
|
||||||
(title || subtitle) &&
|
(title || subtitle) &&
|
||||||
<div className={[styles.default, styles.paddingHorizontal15PX, styles.paddingVertical10PX, styles.borderColorSubtle, styles.borderBottom1PX].join(' ')}>
|
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.bordercolorSecondary, _s.borderBottom1PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.flexRow, styles.alignItemsCenter].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
|
||||||
<h1 className={[styles.default, styles.text, styles.fontWeightBold, styles.colorBlack, styles.fontSize16PX].join(' ')}>{title}</h1>
|
<Heading size='h3'>
|
||||||
|
{title}
|
||||||
|
</Heading>
|
||||||
{
|
{
|
||||||
!!button &&
|
(!!buttonTitle && (!!buttonAction || !!buttonTo)) &&
|
||||||
<div className={[styles.default, styles.marginLeftAuto].join(' ')}>
|
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
|
||||||
{button}
|
<Button
|
||||||
|
text
|
||||||
|
to={buttonTo}
|
||||||
|
onClick={buttonAction}
|
||||||
|
className={[_s.default, _s.cursorPointer, _s.fontWeightBold, _s.text, _s.colorBrand, _s.fontSize13PX, _s.noUnderline].join(' ')}
|
||||||
|
>
|
||||||
|
{buttonTitle}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
{subtitle && <h2 className={[styles.default, styles.text, styles.colorSubtle, styles.fontSize13PX, styles.fontWeightBold, styles.marginTop5PX].join(' ')}>{subtitle}</h2>}
|
{
|
||||||
|
subtitle &&
|
||||||
|
<Heading size='h4'>
|
||||||
|
{subtitle}
|
||||||
|
</Heading>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className={[styles.default, styles.paddingHorizontal15PX, styles.paddingVertical10PX].join(' ')}>
|
|
||||||
{children}
|
{
|
||||||
</div>
|
!noPadding &&
|
||||||
|
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
noPadding && children
|
||||||
|
}
|
||||||
</aside>
|
</aside>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,9 @@ class SignUpPanel extends PureComponent {
|
|||||||
title={intl.formatMessage(messages.title)}
|
title={intl.formatMessage(messages.title)}
|
||||||
subtitle={intl.formatMessage(messages.subtitle)}
|
subtitle={intl.formatMessage(messages.subtitle)}
|
||||||
>
|
>
|
||||||
<Button href="/auth/sign_up">{intl.formatMessage(messages.register)}</Button>
|
<Button href="/auth/sign_up">
|
||||||
|
{intl.formatMessage(messages.register)}
|
||||||
|
</Button>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,50 @@
|
|||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
import { fetchTrends } from '../../actions/trends';
|
// import { fetchTrends } from '../../actions/trends'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import TrendingItem from '../../components/trending_item';
|
import TrendingItem from '../../components/trending_item'
|
||||||
import PanelLayout from './panel_layout';
|
import PanelLayout from './panel_layout'
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id:'trends.title', defaultMessage: 'Trends for you' },
|
title: { id:'trends.title', defaultMessage: 'Trending right now' },
|
||||||
});
|
})
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
// const mapStateToProps = state => ({
|
||||||
trends: state.getIn(['trends', 'items']),
|
// trends: state.getIn(['trends', 'items']),
|
||||||
});
|
// })
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
// const mapDispatchToProps = dispatch => {
|
||||||
return {
|
// return {
|
||||||
fetchTrends: () => dispatch(fetchTrends()),
|
// fetchTrends: () => dispatch(fetchTrends()),
|
||||||
}
|
// }
|
||||||
};
|
// }
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default
|
||||||
|
// @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class TrendsPanel extends ImmutablePureComponent {
|
class TrendsPanel extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
trends: ImmutablePropTypes.list.isRequired,
|
trends: ImmutablePropTypes.list.isRequired,
|
||||||
fetchTrends: PropTypes.func.isRequired,
|
// fetchTrends: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this.props.fetchTrends();
|
// this.props.fetchTrends()
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, trends } = this.props;
|
const { intl, trends } = this.props
|
||||||
|
|
||||||
// !!! TESTING !!!
|
// !!! TESTING !!!
|
||||||
// if (trends.isEmpty()) {
|
// if (trends.isEmpty()) {
|
||||||
// return null;
|
// return null
|
||||||
// }
|
// }
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout title={intl.formatMessage(messages.title)}>
|
<PanelLayout title={intl.formatMessage(messages.title)}>
|
||||||
<div className={styles.default}>
|
<div className={_s.default}>
|
||||||
{ /* trends && trends.map(hashtag => (
|
{ /* trends && trends.map(hashtag => (
|
||||||
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
|
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
|
||||||
)) */ }
|
)) */ }
|
||||||
@ -54,6 +55,6 @@ class TrendsPanel extends ImmutablePureComponent {
|
|||||||
<TrendingItem />
|
<TrendingItem />
|
||||||
</div>
|
</div>
|
||||||
</PanelLayout>
|
</PanelLayout>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
};
|
}
|
87
app/javascript/gabsocial/components/panel/user_panel.js
Normal file
87
app/javascript/gabsocial/components/panel/user_panel.js
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import { autoPlayGif, me } from '../../initial_state'
|
||||||
|
import { makeGetAccount } from '../../selectors'
|
||||||
|
import { shortNumberFormat } from '../../utils/numbers'
|
||||||
|
import DisplayName from '../display_name'
|
||||||
|
import Avatar from '../avatar'
|
||||||
|
import Image from '../image'
|
||||||
|
import UserStat from '../user_stat'
|
||||||
|
import PanelLayout from './panel_layout'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
gabs: { id: 'account.posts', defaultMessage: 'Gabs' },
|
||||||
|
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
||||||
|
follows: { id: 'account.follows', defaultMessage: 'Follows' }
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const getAccount = makeGetAccount()
|
||||||
|
|
||||||
|
return {
|
||||||
|
account: getAccount(state, me),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
@injectIntl
|
||||||
|
class UserPanel extends ImmutablePureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { account, intl } = this.props
|
||||||
|
const displayNameHtml = { __html: account.get('display_name_html') }
|
||||||
|
|
||||||
|
const statItems = [
|
||||||
|
{
|
||||||
|
to: `/${account.get('acct')}`,
|
||||||
|
title: intl.formatMessage(messages.gabs),
|
||||||
|
value: shortNumberFormat(account.get('statuses_count')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: `/${account.get('acct')}/followers`,
|
||||||
|
title: intl.formatMessage(messages.followers),
|
||||||
|
value: shortNumberFormat(account.get('followers_count')),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
to: `/${account.get('acct')}/following`,
|
||||||
|
title: intl.formatMessage(messages.follows),
|
||||||
|
value: shortNumberFormat(account.get('following_count')),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PanelLayout noPadding>
|
||||||
|
<Image
|
||||||
|
className={_s.height122PX}
|
||||||
|
src={account.get('header_static')}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
className={[_s.default, _s.flexRow, _s.paddingVertical10PX, _s.paddingHorizontal15PX, _s.noUnderline].join(' ')}
|
||||||
|
to={`/${account.get('acct')}`}
|
||||||
|
>
|
||||||
|
<div className={[_s.default, _s.marginTopNeg30PX, _s.circle, _s.borderColorWhite, _s.border2PX].join(' ')}>
|
||||||
|
<Avatar account={account} size={62} />
|
||||||
|
</div>
|
||||||
|
<div className={[_s.default, _s.marginLeft15PX].join(' ')}>
|
||||||
|
<DisplayName account={account} multiline />
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
|
||||||
|
<div className={[_s.default, _s.marginBottom15PX, _s.marginTop5PX, _s.flexRow, _s.paddingHorizontal15PX].join(' ')}>
|
||||||
|
{
|
||||||
|
statItems.map((statItem, i) => (
|
||||||
|
<UserStat {...statItem} key={`user-stat-panel-item-${i}`} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</PanelLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -41,8 +41,6 @@ class WhoToFollowPanel extends ImmutablePureComponent {
|
|||||||
// : testing!!! :
|
// : testing!!! :
|
||||||
const suggestions = [
|
const suggestions = [
|
||||||
"1",
|
"1",
|
||||||
"1",
|
|
||||||
"1",
|
|
||||||
]
|
]
|
||||||
// if (suggestions.isEmpty()) {
|
// if (suggestions.isEmpty()) {
|
||||||
// return null;
|
// return null;
|
||||||
@ -50,7 +48,7 @@ class WhoToFollowPanel extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<PanelLayout title={intl.formatMessage(messages.title)}>
|
<PanelLayout title={intl.formatMessage(messages.title)}>
|
||||||
<div className={styles.default}>
|
<div className={_s.default}>
|
||||||
{suggestions && suggestions.map(accountId => (
|
{suggestions && suggestions.map(accountId => (
|
||||||
<AccountContainer
|
<AccountContainer
|
||||||
key={accountId}
|
key={accountId}
|
||||||
|
@ -10,8 +10,8 @@ export default class Permalink extends PureComponent {
|
|||||||
className: PropTypes.string,
|
className: PropTypes.string,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
blank: PropTypes.boolean,
|
blank: PropTypes.bool,
|
||||||
button: PropTypes.boolean,
|
button: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
handleClick = e => {
|
handleClick = e => {
|
||||||
|
9
app/javascript/gabsocial/components/popover/popover.js
Normal file
9
app/javascript/gabsocial/components/popover/popover.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export default class Popover extends PureComponent {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{ /* */ }
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
import Text from './text'
|
||||||
|
|
||||||
export default class ProgressBar extends PureComponent {
|
export default class ProgressBar extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
progress: PropTypes.number,
|
progress: PropTypes.number,
|
||||||
@ -10,11 +12,16 @@ export default class ProgressBar extends PureComponent {
|
|||||||
const style = {
|
const style = {
|
||||||
width: `${completed}%`,
|
width: `${completed}%`,
|
||||||
}
|
}
|
||||||
|
const title = `${completed}% covered this month`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a href='https://shop.dissenter.com/category/donations' className={[styles.default, styles.backgroundPanel, styles.noUnderline, styles.circle, styles.overflowHidden, styles.height22PX, styles.cursorPointer].join(' ')}>
|
<a href='https://shop.dissenter.com/category/donations' className={[_s.default, _s.backgroundPanel, _s.noUnderline, _s.circle, _s.overflowHidden, _s.height22PX, _s.cursorPointer].join(' ')}>
|
||||||
<div className={[styles.default, styles.backgroundColorBrandDark, styles.circle, styles.height22PX].join(' ')} style={style} />
|
<div className={[_s.default, _s.backgroundColorBrandDark, _s.circle, _s.height22PX].join(' ')} style={style} />
|
||||||
<span className={[styles.default, styles.text, styles.width100PC, styles.textAlignCenter, styles.colorWhite, styles.fontSize13PX, styles.positionAbsolute, styles.fontWeightBold, styles.displayFlex, styles.height100PC, styles.justifyContentCenter].join(' ')}>{completed}% covered this month</span>
|
<div className={[_s.default, _s.positionAbsolute, _s.width100PC, _s.height22PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
|
<Text size='small' weight='bold' color='white'>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ export default class ScrollableList extends PureComponent {
|
|||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
showLoading: PropTypes.bool,
|
showLoading: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
emptyMessage: PropTypes.node,
|
emptyMessage: PropTypes.string,
|
||||||
children: PropTypes.node,
|
children: PropTypes.node,
|
||||||
onScrollToTop: PropTypes.func,
|
onScrollToTop: PropTypes.func,
|
||||||
onScroll: PropTypes.func,
|
onScroll: PropTypes.func,
|
||||||
|
@ -3,6 +3,8 @@ import Overlay from 'react-overlays/lib/Overlay';
|
|||||||
import Icon from '../icon';
|
import Icon from '../icon';
|
||||||
import SearchPopout from '../search_popout';
|
import SearchPopout from '../search_popout';
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
export default class Search extends PureComponent {
|
export default class Search extends PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -10,17 +12,17 @@ export default class Search extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
value: PropTypes.string.isRequired,
|
// value: PropTypes.string.isRequired,
|
||||||
submitted: PropTypes.bool,
|
submitted: PropTypes.bool,
|
||||||
onShow: PropTypes.func.isRequired,
|
// onShow: PropTypes.func.isRequired,
|
||||||
openInRoute: PropTypes.bool,
|
openInRoute: PropTypes.bool,
|
||||||
placeholder: PropTypes.string.isRequired,
|
// placeholder: PropTypes.string.isRequired,
|
||||||
searchTitle: PropTypes.string,
|
searchTitle: PropTypes.string,
|
||||||
onChange: PropTypes.func.isRequired,
|
// onChange: PropTypes.func.isRequired,
|
||||||
onKeyUp: PropTypes.func.isRequired,
|
// onKeyUp: PropTypes.func.isRequired,
|
||||||
handleSubmit: PropTypes.func,
|
handleSubmit: PropTypes.func,
|
||||||
withOverlay: PropTypes.bool,
|
withOverlay: PropTypes.bool,
|
||||||
handleClear: PropTypes.func.isRequired,
|
// handleClear: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -42,8 +44,6 @@ export default class Search extends PureComponent {
|
|||||||
|
|
||||||
const hasValue = value ? value.length > 0 || submitted : 0;
|
const hasValue = value ? value.length > 0 || submitted : 0;
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const btnClasses = cx({
|
const btnClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
cursorPointer: 1,
|
cursorPointer: 1,
|
||||||
@ -56,11 +56,11 @@ export default class Search extends PureComponent {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.justifyContentCenter, styles.height53PX].join(' ')}>
|
<div className={[_s.default, _s.justifyContentCenter, _s.height53PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.backgroundWhite, styles.border1PX, styles.borderColorSubtle, styles.flexRow, styles.circle, styles.alignItemsCenter].join(' ')}>
|
<div className={[_s.default, _s.backgroundWhite, _s.border1PX, _s.bordercolorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
|
||||||
<Icon id='search' width='16px' height='16px' className={[styles.default, styles.marginLeft15PX, styles.marginRight10PX].join(' ')} />
|
<Icon id='search' width='16px' height='16px' className={[_s.default, _s.marginLeft15PX, _s.marginRight10PX].join(' ')} />
|
||||||
<input
|
<input
|
||||||
className={[styles.default, styles.text, styles.outlineFocusBrand, styles.lineHeight125, styles.displayBlock, styles.paddingVertical10PX, styles.paddingHorizontal10PX, styles.backgroundTransparent, styles.fontSize15PX, styles.flexGrow1].join(' ')}
|
className={[_s.default, _s.text, _s.outlineFocusBrand, _s.lineHeight125, _s.displayBlock, _s.paddingVertical10PX, _s.paddingHorizontal10PX, _s.backgroundTransparent, _s.fontSize15PX, _s.flexGrow1].join(' ')}
|
||||||
type='text'
|
type='text'
|
||||||
placeholder='Search on Gab...'
|
placeholder='Search on Gab...'
|
||||||
value={value}
|
value={value}
|
||||||
@ -70,7 +70,7 @@ export default class Search extends PureComponent {
|
|||||||
onBlur={this.handleBlur}
|
onBlur={this.handleBlur}
|
||||||
/>
|
/>
|
||||||
<div role='button' tabIndex='0' className={btnClasses} onClick={handleClear}>
|
<div role='button' tabIndex='0' className={btnClasses} onClick={handleClear}>
|
||||||
<Icon id='close' width='10px' height='10px' className={styles.fillColorWhite} aria-label={placeholder} />
|
<Icon id='close' width='10px' height='10px' className={_s.fillColorWhite} aria-label={placeholder} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
225
app/javascript/gabsocial/components/sidebar.js
Normal file
225
app/javascript/gabsocial/components/sidebar.js
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl'
|
||||||
|
import Button from './button'
|
||||||
|
import { closeSidebar } from '../actions/sidebar'
|
||||||
|
import { me } from '../initial_state'
|
||||||
|
import { makeGetAccount } from '../selectors'
|
||||||
|
import GabLogo from './assets/gab_logo'
|
||||||
|
import SidebarSectionTitle from './sidebar_section_title'
|
||||||
|
import SidebarSectionItem from './sidebar_section_item'
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
||||||
|
follows: { id: 'account.follows', defaultMessage: 'Follows' },
|
||||||
|
profile: { id: 'account.profile', defaultMessage: 'Profile' },
|
||||||
|
preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' },
|
||||||
|
follow_requests: { id: 'navigation_bar.follow_requests', defaultMessage: 'Follow requests' },
|
||||||
|
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
|
||||||
|
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
|
||||||
|
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
|
||||||
|
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
|
||||||
|
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
|
||||||
|
lists: { id: 'column.lists', defaultMessage: 'Lists', },
|
||||||
|
apps: { id: 'tabs_bar.apps', defaultMessage: 'Apps' },
|
||||||
|
more: { id: 'sidebar.more', defaultMessage: 'More' },
|
||||||
|
pro: { id: 'promo.gab_pro', defaultMessage: 'Upgrade to GabPRO' },
|
||||||
|
trends: { id: 'promo.trends', defaultMessage: 'Trends' },
|
||||||
|
search: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||||
|
shop: { id: 'tabs_bar.shop', defaultMessage: 'Store - Buy Merch' },
|
||||||
|
donate: { id: 'tabs_bar.donate', defaultMessage: 'Make a Donation' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const getAccount = makeGetAccount()
|
||||||
|
|
||||||
|
return {
|
||||||
|
account: getAccount(state, me),
|
||||||
|
sidebarOpen: state.get('sidebar').sidebarOpen,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onClose() {
|
||||||
|
dispatch(closeSidebar())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
@injectIntl
|
||||||
|
class Sidebar extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
account: ImmutablePropTypes.map,
|
||||||
|
sidebarOpen: PropTypes.bool,
|
||||||
|
onClose: PropTypes.func.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
moreOpen: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
if (!me) return
|
||||||
|
|
||||||
|
if (this.props.sidebarOpen) {
|
||||||
|
document.body.classList.add('with-modals--active')
|
||||||
|
} else {
|
||||||
|
document.body.classList.remove('with-modals--active')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleMore = () => {
|
||||||
|
this.setState({
|
||||||
|
moreOpen: !this.state.moreOpen
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSidebarClose = () => {
|
||||||
|
this.props.onClose()
|
||||||
|
this.setState({
|
||||||
|
moreOpen: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { sidebarOpen, intl, account } = this.props
|
||||||
|
const { moreOpen } = this.state
|
||||||
|
|
||||||
|
if (!me || !account) return null
|
||||||
|
|
||||||
|
const acct = account.get('acct')
|
||||||
|
const isPro = account.get('is_pro')
|
||||||
|
|
||||||
|
const moreIcon = moreOpen ? 'minus' : 'plus'
|
||||||
|
const moreContainerStyle = { display: moreOpen ? 'block' : 'none' }
|
||||||
|
|
||||||
|
const menuItems = [
|
||||||
|
{
|
||||||
|
title: 'Home',
|
||||||
|
icon: 'home',
|
||||||
|
to: '/',
|
||||||
|
count: 124,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Notifications',
|
||||||
|
icon: 'notifications',
|
||||||
|
to: '/notifications',
|
||||||
|
count: 40,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Groups',
|
||||||
|
icon: 'group',
|
||||||
|
to: '/groups',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Lists',
|
||||||
|
icon: 'list',
|
||||||
|
to: '/lists',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Chat',
|
||||||
|
icon: 'chat',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'More',
|
||||||
|
icon: 'more',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const shortcutItems = [
|
||||||
|
{
|
||||||
|
title: 'Meme Group',
|
||||||
|
icon: 'group',
|
||||||
|
to: '/',
|
||||||
|
count: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '@andrew',
|
||||||
|
image: 'http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139',
|
||||||
|
to: '/',
|
||||||
|
count: 3,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const exploreItems = [
|
||||||
|
{
|
||||||
|
title: 'Apps',
|
||||||
|
icon: 'apps',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Shop',
|
||||||
|
icon: 'shop',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Trends',
|
||||||
|
icon: 'trends',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Dissenter',
|
||||||
|
icon: 'dissenter',
|
||||||
|
to: '/',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
return (
|
||||||
|
<header role='banner' className={[_s.default, _s.flexGrow1, _s.z3, _s.alignItemsEnd].join(' ')}>
|
||||||
|
<div className={[_s.default, _s.width240PX].join(' ')}>
|
||||||
|
<div className={[_s.default, _s.positionFixed, _s.top0, _s.height100PC].join(' ')}>
|
||||||
|
<div className={[_s.default, _s.height100PC, _s.width240PX, _s.paddingRight15PX, _s.marginVertical10PX].join(' ')}>
|
||||||
|
|
||||||
|
<h1 className={[_s.default].join(' ')}>
|
||||||
|
<NavLink to='/' aria-label='Gab' className={[_s.default, _s.noSelect, _s.noUnderline, _s.height50PX, _s.justifyContentCenter, _s.cursorPointer, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
|
<GabLogo />
|
||||||
|
</NavLink>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<SidebarSectionItem
|
||||||
|
image='http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139'
|
||||||
|
title='@admin'
|
||||||
|
to='/profile'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<nav aria-label='Primary' role='navigation' className={[_s.default, _s.width100PC, _s.marginBottom15PX].join(' ')}>
|
||||||
|
<SidebarSectionTitle>Menu</SidebarSectionTitle>
|
||||||
|
{
|
||||||
|
menuItems.map((menuItem, i) => (
|
||||||
|
<SidebarSectionItem {...menuItem} key={`sidebar-item-menu-${i}`} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<SidebarSectionTitle>Shortcuts</SidebarSectionTitle>
|
||||||
|
{
|
||||||
|
shortcutItems.map((shortcutItem, i) => (
|
||||||
|
<SidebarSectionItem {...shortcutItem} key={`sidebar-item-shortcut-${i}`} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
<SidebarSectionTitle>Explore</SidebarSectionTitle>
|
||||||
|
{
|
||||||
|
exploreItems.map((exploreItem, i) => (
|
||||||
|
<SidebarSectionItem {...exploreItem} key={`sidebar-item-explore-${i}`} />
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
block
|
||||||
|
className={[_s.paddingVertical15PX, _s.fontSize15PX, _s.fontWeightBold].join(' ')}
|
||||||
|
>
|
||||||
|
Gab
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -47,9 +47,11 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
export default @connect(mapStateToProps, mapDispatchToProps)
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Header extends ImmutablePureComponent {
|
class Sidebar extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
@ -175,12 +177,10 @@ class Header extends ImmutablePureComponent {
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const titleClasses = cx({
|
const titleClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
text: 1,
|
text: 1,
|
||||||
colorSubtle: 1,
|
colorSecondary: 1,
|
||||||
displayBlock: 1,
|
displayBlock: 1,
|
||||||
fontSize13PX: 1,
|
fontSize13PX: 1,
|
||||||
paddingVertical5PX: 1,
|
paddingVertical5PX: 1,
|
||||||
@ -190,19 +190,19 @@ class Header extends ImmutablePureComponent {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header role='banner' className={[styles.default, styles.flexGrow1, styles.z3, styles.alignItemsEnd].join(' ')}>
|
<header role='banner' className={[_s.default, _s.flexGrow1, _s.z3, _s.alignItemsEnd].join(' ')}>
|
||||||
<div className={[styles.default, styles.width250PX].join(' ')}>
|
<div className={[_s.default, _s.width240PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.positionFixed, styles.top0, styles.height100PC].join(' ')}>
|
<div className={[_s.default, _s.positionFixed, _s.top0, _s.height100PC].join(' ')}>
|
||||||
<div className={[styles.default, styles.height100PC, styles.width250PX, styles.paddingHorizontal15PX, styles.marginVertical10PX].join(' ')}>
|
<div className={[_s.default, _s.height100PC, _s.width240PX, _s.paddingRight15PX, _s.marginVertical10PX].join(' ')}>
|
||||||
<h1 className={[styles.default].join(' ')}>
|
<h1 className={[_s.default].join(' ')}>
|
||||||
<NavLink to='/' aria-label='Gab' className={[styles.default, styles.noSelect, styles.noUnderline, styles.height50PX, styles.justifyContentCenter, styles.cursorPointer, styles.paddingHorizontal10PX].join(' ')}>
|
<NavLink to='/' aria-label='Gab' className={[_s.default, _s.noSelect, _s.noUnderline, _s.height50PX, _s.justifyContentCenter, _s.cursorPointer, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<GabLogo />
|
<GabLogo />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</h1>
|
</h1>
|
||||||
<div>
|
<div>
|
||||||
<HeaderMenuItem me image='http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139' title='@admin' to='/profile' />
|
<HeaderMenuItem me image='http://localhost:3000/system/accounts/avatars/000/000/001/original/260e8c96c97834da.jpeg?1562898139' title='@admin' to='/profile' />
|
||||||
</div>
|
</div>
|
||||||
<nav aria-label='Primary' role='navigation' className={[styles.default, styles.width100PC, styles.marginBottom15PX].join(' ')}>
|
<nav aria-label='Primary' role='navigation' className={[_s.default, _s.width100PC, _s.marginBottom15PX].join(' ')}>
|
||||||
<span className={titleClasses}>Menu</span>
|
<span className={titleClasses}>Menu</span>
|
||||||
{
|
{
|
||||||
menuItems.map((menuItem, i) => (
|
menuItems.map((menuItem, i) => (
|
||||||
@ -222,7 +222,7 @@ class Header extends ImmutablePureComponent {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
</nav>
|
</nav>
|
||||||
<Button block className={[styles.paddingVertical15PX, styles.fontSize15PX, styles.fontWeightBold].join(' ')}>
|
<Button block className={[_s.paddingVertical15PX, _s.fontSize15PX, _s.fontWeightBold].join(' ')}>
|
||||||
Gab
|
Gab
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -241,7 +241,7 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
image: PropTypes.string,
|
image: PropTypes.string,
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
me: PropTypes.boolean,
|
me: PropTypes.bool,
|
||||||
count: PropTypes.number,
|
count: PropTypes.number,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,8 +261,6 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
const { to, active, icon, image, title, me, count } = this.props
|
const { to, active, icon, image, title, me, count } = this.props
|
||||||
const { hovering } = this.state
|
const { hovering } = this.state
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const iconSize = '16px'
|
const iconSize = '16px'
|
||||||
const shouldShowActive = hovering || active
|
const shouldShowActive = hovering || active
|
||||||
const isNotifications = to === '/notifications'
|
const isNotifications = to === '/notifications'
|
||||||
@ -277,7 +275,7 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
alignItemsCenter: 1,
|
alignItemsCenter: 1,
|
||||||
radiusSmall: 1,
|
radiusSmall: 1,
|
||||||
// border1PX: shouldShowActive,
|
// border1PX: shouldShowActive,
|
||||||
// borderColorSubtle: shouldShowActive,
|
// bordercolorSecondary: shouldShowActive,
|
||||||
backgroundSubtle2: shouldShowActive,
|
backgroundSubtle2: shouldShowActive,
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -287,13 +285,13 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
fontSize15PX: 1,
|
fontSize15PX: 1,
|
||||||
text: 1,
|
text: 1,
|
||||||
textOverflowEllipsis: 1,
|
textOverflowEllipsis: 1,
|
||||||
colorBlack: shouldShowActive || me,
|
colorPrimary: shouldShowActive || me,
|
||||||
colorSubtle: !hovering && !active && !me,
|
colorSecondary: !hovering && !active && !me,
|
||||||
})
|
})
|
||||||
|
|
||||||
const iconClasses = cx({
|
const iconClasses = cx({
|
||||||
fillColorBlack: shouldShowActive,
|
fillColorBlack: shouldShowActive,
|
||||||
fillColorSubtle: !hovering && !active,
|
fillcolorSecondary: !hovering && !active,
|
||||||
})
|
})
|
||||||
|
|
||||||
const countClasses = cx({
|
const countClasses = cx({
|
||||||
@ -305,7 +303,7 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
marginRight2PX: 1,
|
marginRight2PX: 1,
|
||||||
lineHeight15: 1,
|
lineHeight15: 1,
|
||||||
marginLeft5PX: 1,
|
marginLeft5PX: 1,
|
||||||
colorSubtle: !isNotifications,
|
colorSecondary: !isNotifications,
|
||||||
colorWhite: isNotifications,
|
colorWhite: isNotifications,
|
||||||
backgroundColorBrand: isNotifications,
|
backgroundColorBrand: isNotifications,
|
||||||
radiusSmall: isNotifications,
|
radiusSmall: isNotifications,
|
||||||
@ -316,21 +314,21 @@ class HeaderMenuItem extends PureComponent {
|
|||||||
to={to}
|
to={to}
|
||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
className={[styles.default, styles.noUnderline, styles.cursorPointer, styles.width100PC, styles.alignItemsStart, styles.flexGrow1].join(' ')}
|
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.alignItemsStart, _s.flexGrow1].join(' ')}
|
||||||
>
|
>
|
||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
<div className={[styles.default]}>
|
<div className={[_s.default]}>
|
||||||
{ icon && <Icon id={icon} className={iconClasses} width={iconSize} height={iconSize} /> }
|
{ icon && <Icon id={icon} className={iconClasses} width={iconSize} height={iconSize} /> }
|
||||||
{ image &&
|
{ image &&
|
||||||
<img
|
<img
|
||||||
className={[styles.default, styles.circle].join(' ')}
|
className={[_s.default, _s.circle].join(' ')}
|
||||||
width={iconSize}
|
width={iconSize}
|
||||||
height={iconSize}
|
height={iconSize}
|
||||||
src={image}
|
src={image}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.default, styles.flexNormal, styles.paddingHorizontal10PX, styles.textOverflowEllipsis, styles.overflowWrapBreakWord, styles.flexRow, styles.width100PC].join(' ')}>
|
<div className={[_s.default, _s.flexNormal, _s.paddingHorizontal10PX, _s.textOverflowEllipsis, _s.overflowWrapBreakWord, _s.flexRow, _s.width100PC].join(' ')}>
|
||||||
<span className={textClasses}>{title}</span>
|
<span className={textClasses}>{title}</span>
|
||||||
</div>
|
</div>
|
||||||
{ count > 0 &&
|
{ count > 0 &&
|
115
app/javascript/gabsocial/components/sidebar_section_item.js
Normal file
115
app/javascript/gabsocial/components/sidebar_section_item.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import classNames from 'classnames/bind'
|
||||||
|
import Button from './button'
|
||||||
|
import Icon from './icon'
|
||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
export default class SidebarSectionItem extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
to: PropTypes.string,
|
||||||
|
active: PropTypes.bool,
|
||||||
|
icon: PropTypes.string,
|
||||||
|
image: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
me: PropTypes.bool,
|
||||||
|
suffix: PropTypes.node,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { to, active, icon, image, title, me, count } = this.props
|
||||||
|
const { hovering } = this.state
|
||||||
|
|
||||||
|
const iconSize = '16px'
|
||||||
|
const shouldShowActive = hovering || active
|
||||||
|
const isNotifications = to === '/notifications'
|
||||||
|
|
||||||
|
const containerClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
maxWidth100PC: 1,
|
||||||
|
width100PC: 1,
|
||||||
|
flexRow: 1,
|
||||||
|
paddingVertical5PX: 1,
|
||||||
|
paddingHorizontal10PX: 1,
|
||||||
|
alignItemsCenter: 1,
|
||||||
|
radiusSmall: 1,
|
||||||
|
// border1PX: shouldShowActive,
|
||||||
|
// bordercolorSecondary: shouldShowActive,
|
||||||
|
backgroundSubtle2: shouldShowActive,
|
||||||
|
})
|
||||||
|
|
||||||
|
const textClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
fontWeightNormal: 1,
|
||||||
|
fontSize15PX: 1,
|
||||||
|
text: 1,
|
||||||
|
textOverflowEllipsis: 1,
|
||||||
|
colorPrimary: shouldShowActive || me,
|
||||||
|
colorSecondary: !hovering && !active && !me,
|
||||||
|
})
|
||||||
|
|
||||||
|
const iconClasses = cx({
|
||||||
|
fillColorBlack: shouldShowActive,
|
||||||
|
fillcolorSecondary: !hovering && !active,
|
||||||
|
})
|
||||||
|
|
||||||
|
const countClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
text: 1,
|
||||||
|
marginLeftAuto: 1,
|
||||||
|
fontSize12PX: 1,
|
||||||
|
paddingHorizontal5PX: 1,
|
||||||
|
marginRight2PX: 1,
|
||||||
|
lineHeight15: 1,
|
||||||
|
marginLeft5PX: 1,
|
||||||
|
colorSecondary: !isNotifications,
|
||||||
|
colorWhite: isNotifications,
|
||||||
|
backgroundColorBrand: isNotifications,
|
||||||
|
radiusSmall: isNotifications,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
|
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.alignItemsStart, _s.flexGrow1].join(' ')}
|
||||||
|
>
|
||||||
|
<div className={containerClasses}>
|
||||||
|
<div className={[_s.default]}>
|
||||||
|
{ icon && <Icon id={icon} className={iconClasses} width={iconSize} height={iconSize} /> }
|
||||||
|
{ image &&
|
||||||
|
<img
|
||||||
|
className={[_s.default, _s.circle].join(' ')}
|
||||||
|
width={iconSize}
|
||||||
|
height={iconSize}
|
||||||
|
src={image}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div className={[_s.default, _s.flexNormal, _s.paddingHorizontal10PX, _s.textOverflowEllipsis, _s.overflowWrapBreakWord, _s.flexRow, _s.width100PC].join(' ')}>
|
||||||
|
<span className={textClasses}>{title}</span>
|
||||||
|
</div>
|
||||||
|
{ count > 0 &&
|
||||||
|
<span className={countClasses}>
|
||||||
|
{count}
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
21
app/javascript/gabsocial/components/sidebar_section_title.js
Normal file
21
app/javascript/gabsocial/components/sidebar_section_title.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
export default class SidebarSectionTitle extends PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
children: PropTypes.string.isRequired,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={[_s.default, _s.paddingVertical5PX, _s.paddingHorizontal10PX, _s.marginTop10PX].join(' ')}>
|
||||||
|
<Text size='small' weight='bold' color='secondary'>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -10,7 +10,6 @@ import Card from '../../features/status/components/card';
|
|||||||
import { MediaGallery, Video } from '../../features/ui/util/async-components';
|
import { MediaGallery, Video } from '../../features/ui/util/async-components';
|
||||||
import Avatar from '../avatar';
|
import Avatar from '../avatar';
|
||||||
import StatusQuote from '../status_quote';
|
import StatusQuote from '../status_quote';
|
||||||
import AvatarOverlay from '../avatar_overlay';
|
|
||||||
import RelativeTimestamp from '../relative_timestamp';
|
import RelativeTimestamp from '../relative_timestamp';
|
||||||
import DisplayName from '../display_name';
|
import DisplayName from '../display_name';
|
||||||
import StatusContent from '../status_content';
|
import StatusContent from '../status_content';
|
||||||
@ -406,8 +405,6 @@ class Status extends ImmutablePureComponent {
|
|||||||
|
|
||||||
if (account === undefined || account === null) {
|
if (account === undefined || account === null) {
|
||||||
statusAvatar = <Avatar account={status.get('account')} size={50} />;
|
statusAvatar = <Avatar account={status.get('account')} size={50} />;
|
||||||
} else {
|
|
||||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlers = this.props.muted
|
const handlers = this.props.muted
|
||||||
@ -430,7 +427,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<HotKeys handlers={handlers}>
|
<HotKeys handlers={handlers}>
|
||||||
<div
|
<div
|
||||||
className={[styles.default, styles.backgroundWhite, styles.radiusSmall, styles.marginBottom15PX, styles.border1PX, styles.borderColorSubtle].join(' ')}
|
className={[_s.default, _s.backgroundWhite, _s.radiusSmall, _s.marginBottom15PX, _s.border1PX, _s.bordercolorSecondary].join(' ')}
|
||||||
tabIndex={this.props.muted ? null : 0}
|
tabIndex={this.props.muted ? null : 0}
|
||||||
data-featured={featured ? 'true' : null}
|
data-featured={featured ? 'true' : null}
|
||||||
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
aria-label={textForScreenReader(intl, status, rebloggedByText)}
|
||||||
@ -448,37 +445,37 @@ class Status extends ImmutablePureComponent {
|
|||||||
data-id={status.get('id')}
|
data-id={status.get('id')}
|
||||||
>
|
>
|
||||||
|
|
||||||
<div className={[styles.default, styles.paddingHorizontal15PX, styles.paddingVertical10PX].join(' ')}>
|
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.flexRow, styles.marginTop5PX].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.marginTop5PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.marginRight10PX].join(' ')}>{statusAvatar}</div>
|
<div className={[_s.default, _s.marginRight10PX].join(' ')}>{statusAvatar}</div>
|
||||||
<div className={[styles.default, styles.alignItemsStart, styles.flexGrow1, styles.marginTop5PX].join(' ')}>
|
<div className={[_s.default, _s.alignItemsStart, _s.flexGrow1, _s.marginTop5PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.flexRow, styles.width100PC, styles.alignItemsStart].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.alignItemsStart].join(' ')}>
|
||||||
<NavLink
|
<NavLink
|
||||||
className={[styles.default, styles.flexRow, styles.alignItemsStart, styles.noUnderline].join(' ')}
|
className={[_s.default, _s.flexRow, _s.alignItemsStart, _s.noUnderline].join(' ')}
|
||||||
to={`/${status.getIn(['account', 'acct'])}`}
|
to={`/${status.getIn(['account', 'acct'])}`}
|
||||||
title={status.getIn(['account', 'acct'])}
|
title={status.getIn(['account', 'acct'])}
|
||||||
>
|
>
|
||||||
<DisplayName account={status.get('account')} />
|
<DisplayName account={status.get('account')} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<Icon id='ellipsis' width='20px' height='20px' className={[styles.default, styles.fillColorSubtle, styles.marginLeftAuto].join(' ')} />
|
<Icon id='ellipsis' width='20px' height='20px' className={[_s.default, _s.fillcolorSecondary, _s.marginLeftAuto].join(' ')} />
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.default, styles.flexRow, styles.alignItemsCenter, styles.lineHeight15].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.lineHeight15].join(' ')}>
|
||||||
<NavLink
|
<NavLink
|
||||||
to={statusUrl}
|
to={statusUrl}
|
||||||
className={[styles.default, styles.text, styles.fontSize13PX, styles.noUnderline, styles.colorSubtle].join(' ')}
|
className={[_s.default, _s.text, _s.fontSize13PX, _s.noUnderline, _s.colorSecondary].join(' ')}
|
||||||
>
|
>
|
||||||
<RelativeTimestamp timestamp={status.get('created_at')} />
|
<RelativeTimestamp timestamp={status.get('created_at')} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
<span className={[styles.default, styles.text, styles.fontSize12PX, styles.marginLeft5PX, styles.colorSubtle].join(' ')}>•</span>
|
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}>•</span>
|
||||||
<Icon id='globe' width='12px' height='12px' className={[styles.default, styles.displayInline, styles.marginLeft5PX, styles.fillColorSubtle].join(' ')}/>
|
<Icon id='globe' width='12px' height='12px' className={[_s.default, _s.displayInline, _s.marginLeft5PX, _s.fillcolorSecondary].join(' ')}/>
|
||||||
|
|
||||||
{
|
{
|
||||||
!!status.get('group') &&
|
!!status.get('group') &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<span className={[styles.default, styles.text, styles.fontSize12PX, styles.marginLeft5PX, styles.colorSubtle].join(' ')}>•</span>
|
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}>•</span>
|
||||||
<NavLink
|
<NavLink
|
||||||
to={`/groups/${status.getIn(['group', 'id'])}`}
|
to={`/groups/${status.getIn(['group', 'id'])}`}
|
||||||
className={[styles.default, styles.text, styles.fontSize13PX, styles.marginLeft5PX, styles.colorBlack].join(' ')}
|
className={[_s.default, _s.text, _s.fontSize13PX, _s.marginLeft5PX, _s.colorPrimary].join(' ')}
|
||||||
>
|
>
|
||||||
{status.getIn(['group', 'title'])}
|
{status.getIn(['group', 'title'])}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
@ -488,10 +485,10 @@ class Status extends ImmutablePureComponent {
|
|||||||
{
|
{
|
||||||
status.get('revised_at') !== null &&
|
status.get('revised_at') !== null &&
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<span className={[styles.default, styles.text, styles.fontSize12PX, styles.marginLeft5PX, styles.colorSubtle].join(' ')}>•</span>
|
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}>•</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => other.onShowRevisions(status)}
|
onClick={() => other.onShowRevisions(status)}
|
||||||
className={[styles.default, styles.text, styles.fontSize13PX, styles.marginLeft5PX, styles.colorSubtle].join(' ')}
|
className={[_s.default, _s.text, _s.fontSize13PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}
|
||||||
>
|
>
|
||||||
Edited
|
Edited
|
||||||
</button>
|
</button>
|
||||||
@ -503,7 +500,7 @@ class Status extends ImmutablePureComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.default}>
|
<div className={_s.default}>
|
||||||
<StatusContent
|
<StatusContent
|
||||||
status={status}
|
status={status}
|
||||||
reblogContent={reblogContent}
|
reblogContent={reblogContent}
|
||||||
|
@ -48,6 +48,8 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
class StatusActionBarItem extends PureComponent {
|
class StatusActionBarItem extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string.isRequired,
|
title: PropTypes.string.isRequired,
|
||||||
@ -60,13 +62,11 @@ class StatusActionBarItem extends PureComponent {
|
|||||||
render() {
|
render() {
|
||||||
const { title, onClick, icon, active, disabled } = this.props
|
const { title, onClick, icon, active, disabled } = this.props
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const btnClasses = cx({
|
const btnClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
text: 1,
|
text: 1,
|
||||||
fontSize13PX: 1,
|
fontSize13PX: 1,
|
||||||
fontWeight500: 1,
|
fontWeightMedium: 1,
|
||||||
cursorPointer: 1,
|
cursorPointer: 1,
|
||||||
displayFlex: 1,
|
displayFlex: 1,
|
||||||
justifyContentCenter: 1,
|
justifyContentCenter: 1,
|
||||||
@ -79,18 +79,18 @@ class StatusActionBarItem extends PureComponent {
|
|||||||
outlineFocusBrand: 1,
|
outlineFocusBrand: 1,
|
||||||
backgroundTransparent: 1,
|
backgroundTransparent: 1,
|
||||||
backgroundSubtle_onHover: 1,
|
backgroundSubtle_onHover: 1,
|
||||||
colorSubtle: 1,
|
colorSecondary: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.flexGrow1, styles.paddingHorizontal10PX].join(' ')}>
|
<div className={[_s.default, _s.flexGrow1, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<button
|
<button
|
||||||
className={btnClasses}
|
className={btnClasses}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
active={active}
|
active={active}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Icon width='16px' height='16px' id={icon} className={[styles.default, styles.marginRight10PX, styles.fillColorSubtle].join(' ')} />
|
<Icon width='16px' height='16px' id={icon} className={[_s.default, _s.marginRight10PX, _s.fillcolorSecondary].join(' ')} />
|
||||||
{title}
|
{title}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -317,20 +317,20 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
{
|
{
|
||||||
title: formatMessage(messages.like),
|
title: formatMessage(messages.like),
|
||||||
icon: 'like',
|
icon: 'like',
|
||||||
active: status.get('favourited'),
|
active: !!status.get('favourited'),
|
||||||
onClick: this.handleFavouriteClick,
|
onClick: this.handleFavouriteClick,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: formatMessage(messages.comment),
|
title: formatMessage(messages.comment),
|
||||||
icon: 'comment',
|
icon: 'comment',
|
||||||
active: 0,
|
active: false,
|
||||||
onClick: this.handleReplyClick,
|
onClick: this.handleReplyClick,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: reblogTitle,
|
title: reblogTitle,
|
||||||
icon: (status.get('visibility') === 'private') ? 'lock' : 'repost',
|
icon: (status.get('visibility') === 'private') ? 'lock' : 'repost',
|
||||||
disabled: !publicStatus,
|
disabled: !publicStatus,
|
||||||
active: status.get('reblogged'),
|
active: !!status.get('reblogged'),
|
||||||
onClick: this.handleReblogClick,
|
onClick: this.handleReblogClick,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -344,8 +344,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
const hasInteractions = favoriteCount > 0 || replyCount > 0 || reblogCount > 0
|
const hasInteractions = favoriteCount > 0 || replyCount > 0 || reblogCount > 0
|
||||||
const shouldCondense = (!!status.get('card') || status.get('media_attachments').size > 0) && !hasInteractions
|
const shouldCondense = (!!status.get('card') || status.get('media_attachments').size > 0) && !hasInteractions
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const containerClasses = cx({
|
const containerClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
paddingHorizontal10PX: 1,
|
paddingHorizontal10PX: 1,
|
||||||
@ -359,14 +357,14 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
flexRow: 1,
|
flexRow: 1,
|
||||||
width100PC: 1,
|
width100PC: 1,
|
||||||
borderTop1PX: !shouldCondense,
|
borderTop1PX: !shouldCondense,
|
||||||
borderColorSubtle: !shouldCondense,
|
bordercolorSecondary: !shouldCondense,
|
||||||
marginTop5PX: hasInteractions,
|
marginTop5PX: hasInteractions,
|
||||||
})
|
})
|
||||||
|
|
||||||
const interactionBtnClasses = cx({
|
const interactionBtnClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
text: 1,
|
text: 1,
|
||||||
colorSubtle: 1,
|
colorSecondary: 1,
|
||||||
cursorPointer: 1,
|
cursorPointer: 1,
|
||||||
fontSize15PX: 1,
|
fontSize15PX: 1,
|
||||||
fontWeightNormal: 1,
|
fontWeightNormal: 1,
|
||||||
@ -378,7 +376,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
<div className={containerClasses}>
|
<div className={containerClasses}>
|
||||||
{
|
{
|
||||||
hasInteractions &&
|
hasInteractions &&
|
||||||
<div className={[styles.default, styles.flexRow, styles.paddingHorizontal5PX].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.paddingHorizontal5PX].join(' ')}>
|
||||||
{ favoriteCount > 0 &&
|
{ favoriteCount > 0 &&
|
||||||
<button className={interactionBtnClasses}>
|
<button className={interactionBtnClasses}>
|
||||||
{favoriteCount}
|
{favoriteCount}
|
||||||
@ -400,7 +398,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div className={innerContainerClasses}>
|
<div className={innerContainerClasses}>
|
||||||
<div className={[styles.default, styles.flexRow, styles.paddingVertical2PX, styles.width100PC].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.paddingVertical2PX, _s.width100PC].join(' ')}>
|
||||||
{
|
{
|
||||||
items.map((item, i) => (
|
items.map((item, i) => (
|
||||||
<StatusActionBarItem key={`status-action-bar-item-${i}`} {...item} />
|
<StatusActionBarItem key={`status-action-bar-item-${i}`} {...item} />
|
||||||
|
@ -15,6 +15,8 @@ const messages = defineMessages({
|
|||||||
readMore: { id: 'status.read_more', defaultMessage: 'Read more' },
|
readMore: { id: 'status.read_more', defaultMessage: 'Read more' },
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
export default
|
export default
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class StatusContent extends ImmutablePureComponent {
|
class StatusContent extends ImmutablePureComponent {
|
||||||
@ -167,15 +169,13 @@ class StatusContent extends ImmutablePureComponent {
|
|||||||
// 'status__content--with-action': this.props.onClick && this.context.router,
|
// 'status__content--with-action': this.props.onClick && this.context.router,
|
||||||
// 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
// 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||||
// 'status__content--collapsed': this.state.collapsed === true,
|
// 'status__content--collapsed': this.state.collapsed === true,
|
||||||
// // styles.paddingHorizontal15PX, styles.marginBottom15PX
|
// // _s.paddingHorizontal15PX, _s.marginBottom15PX
|
||||||
// });
|
// });
|
||||||
|
|
||||||
if (isRtl(status.get('search_index'))) {
|
if (isRtl(status.get('search_index'))) {
|
||||||
directionStyle.direction = 'rtl';
|
directionStyle.direction = 'rtl';
|
||||||
}
|
}
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
if (status.get('spoiler_text').length > 0) {
|
if (status.get('spoiler_text').length > 0) {
|
||||||
let mentionsPlaceholder = '';
|
let mentionsPlaceholder = '';
|
||||||
|
|
||||||
@ -217,7 +217,7 @@ class StatusContent extends ImmutablePureComponent {
|
|||||||
<div
|
<div
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
className={[styles.statusContent].join(' ')}
|
className={[_s.statusContent].join(' ')}
|
||||||
style={directionStyle}
|
style={directionStyle}
|
||||||
dangerouslySetInnerHTML={content}
|
dangerouslySetInnerHTML={content}
|
||||||
lang={status.get('language')}
|
lang={status.get('language')}
|
||||||
@ -227,7 +227,7 @@ class StatusContent extends ImmutablePureComponent {
|
|||||||
{
|
{
|
||||||
this.state.collapsed &&
|
this.state.collapsed &&
|
||||||
<button
|
<button
|
||||||
className={[styles.default, styles.displayFlex, styles.cursorPointer, styles.paddingVertical2PX, styles.text, styles.colorBlack, styles.fontWeightBold, styles.fontSize15PX].join(' ')}
|
className={[_s.default, _s.displayFlex, _s.cursorPointer, _s.paddingVertical2PX, _s.text, _s.colorPrimary, _s.fontWeightBold, _s.fontSize15PX].join(' ')}
|
||||||
onClick={this.props.onClick}
|
onClick={this.props.onClick}
|
||||||
>
|
>
|
||||||
{intl.formatMessage(messages.readMore)}
|
{intl.formatMessage(messages.readMore)}
|
||||||
@ -241,7 +241,7 @@ class StatusContent extends ImmutablePureComponent {
|
|||||||
<div
|
<div
|
||||||
tabIndex='0'
|
tabIndex='0'
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
className={[styles.paddingHorizontal15PX, styles.marginBottom15PX, styles.statusContent].join(' ')}
|
className={[_s.paddingHorizontal15PX, _s.marginBottom15PX, _s.statusContent].join(' ')}
|
||||||
style={directionStyle}
|
style={directionStyle}
|
||||||
dangerouslySetInnerHTML={content}
|
dangerouslySetInnerHTML={content}
|
||||||
lang={status.get('language')}
|
lang={status.get('language')}
|
||||||
|
@ -17,7 +17,7 @@ export default class StatusList extends ImmutablePureComponent {
|
|||||||
isLoading: PropTypes.bool,
|
isLoading: PropTypes.bool,
|
||||||
isPartial: PropTypes.bool,
|
isPartial: PropTypes.bool,
|
||||||
hasMore: PropTypes.bool,
|
hasMore: PropTypes.bool,
|
||||||
emptyMessage: PropTypes.node,
|
emptyMessage: PropTypes.string,
|
||||||
timelineId: PropTypes.string,
|
timelineId: PropTypes.string,
|
||||||
queuedItemSize: PropTypes.number,
|
queuedItemSize: PropTypes.number,
|
||||||
onDequeueTimeline: PropTypes.func,
|
onDequeueTimeline: PropTypes.func,
|
||||||
|
79
app/javascript/gabsocial/components/text.js
Normal file
79
app/javascript/gabsocial/components/text.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import classNames from 'classnames/bind'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
const COLORS = {
|
||||||
|
primary: 'primary',
|
||||||
|
secondary: 'secondary',
|
||||||
|
brand: 'brand',
|
||||||
|
error: 'error',
|
||||||
|
white: 'white',
|
||||||
|
}
|
||||||
|
|
||||||
|
const SIZES = {
|
||||||
|
extraSmall: 'extraSmall',
|
||||||
|
small: 'small',
|
||||||
|
normal: 'normal',
|
||||||
|
medium: 'medium',
|
||||||
|
large: 'large',
|
||||||
|
extraLarge: 'extraLarge',
|
||||||
|
}
|
||||||
|
|
||||||
|
const WEIGHTS = {
|
||||||
|
normal: 'normal',
|
||||||
|
medium: 'medium',
|
||||||
|
bold: 'bold',
|
||||||
|
extraBold: 'extraBold',
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Text extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
tagName: PropTypes.string,
|
||||||
|
className: PropTypes.string,
|
||||||
|
children: PropTypes.any,
|
||||||
|
color: PropTypes.oneOf(Object.keys(COLORS)),
|
||||||
|
size: PropTypes.oneOf(Object.keys(SIZES)),
|
||||||
|
weight: PropTypes.oneOf(Object.keys(WEIGHTS)),
|
||||||
|
underline: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
tagName: 'span',
|
||||||
|
color: COLORS.primary,
|
||||||
|
size: SIZES.normal,
|
||||||
|
weight: WEIGHTS.normal,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { tagName, className, children, color, size, weight, underline } = this.props
|
||||||
|
|
||||||
|
const classes = cx(className, {
|
||||||
|
default: 1,
|
||||||
|
text: 1,
|
||||||
|
|
||||||
|
colorPrimary: color === COLORS.primary,
|
||||||
|
colorSecondary: color === COLORS.secondary,
|
||||||
|
colorBrand: color === COLORS.brand,
|
||||||
|
colorWhite: color === COLORS.white,
|
||||||
|
|
||||||
|
fontSize19PX: size === SIZES.large,
|
||||||
|
fontSize15PX: size === SIZES.medium,
|
||||||
|
fontSize14PX: size === SIZES.normal,
|
||||||
|
fontSize13PX: size === SIZES.small,
|
||||||
|
|
||||||
|
fontWeightNormal: weight === WEIGHTS.normal,
|
||||||
|
fontWeightMedium: weight === WEIGHTS.medium,
|
||||||
|
fontWeightBold: weight === WEIGHTS.bold,
|
||||||
|
|
||||||
|
underline: underline,
|
||||||
|
})
|
||||||
|
|
||||||
|
return React.createElement(
|
||||||
|
tagName,
|
||||||
|
{
|
||||||
|
className: classes,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -26,14 +26,14 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
|||||||
const { account, size, ...rest } = this.props;
|
const { account, size, ...rest } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className={[styles.default, styles.overflowHidden, styles.radiusSmall, styles.border1PX, styles.borderColorSubtle, styles.backgroundWhite, styles.marginBottom15PX].join(' ')}>
|
<section className={[_s.default, _s.overflowHidden, _s.radiusSmall, _s.border1PX, _s.bordercolorSecondary, _s.backgroundWhite, _s.marginBottom15PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.backgroundSubtle, styles.borderBottom1PX, styles.borderColorSubtle, styles.paddingHorizontal15PX, styles.paddingVertical2PX].join(' ')}>
|
<div className={[_s.default, _s.backgroundSubtle, _s.borderBottom1PX, _s.bordercolorSecondary, _s.paddingHorizontal15PX, _s.paddingVertical2PX].join(' ')}>
|
||||||
<h1 className={[styles.default, styles.text, styles.colorSubtle, styles.fontSize12PX, styles.fontWeight500, styles.lineHeight2, styles.paddingVertical2PX].join(' ')}>
|
<h1 className={[_s.default, _s.text, _s.colorSecondary, _s.fontSize12PX, _s.fontWeightMedium, _s.lineHeight2, _s.paddingVertical2PX].join(' ')}>
|
||||||
Create Post
|
Create Post
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
<div className={[styles.default, styles.flexRow, styles.paddingVertical15PX, styles.paddingHorizontal15PX].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.paddingVertical15PX, _s.paddingHorizontal15PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.marginRight10PX].join(' ')}>
|
<div className={[_s.default, _s.marginRight10PX].join(' ')}>
|
||||||
<Avatar account={account} size={46} />
|
<Avatar account={account} size={46} />
|
||||||
</div>
|
</div>
|
||||||
<ComposeFormContainer {...rest} />
|
<ComposeFormContainer {...rest} />
|
||||||
|
@ -5,10 +5,12 @@ import { NavLink } from 'react-router-dom'
|
|||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import { shortNumberFormat } from '../utils/numbers'
|
import { shortNumberFormat } from '../utils/numbers'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
export default class TrendingItem extends ImmutablePureComponent {
|
export default class TrendingItem extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
hashtag: ImmutablePropTypes.map.isRequired,
|
trend: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
@ -24,28 +26,27 @@ export default class TrendingItem extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hashtag } = this.props
|
const { trend } = this.props
|
||||||
const { hovering } = this.state
|
const { hovering } = this.state
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
const subtitleClasses = cx({
|
const subtitleClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
text: 1,
|
text: 1,
|
||||||
displayFlex: 1,
|
displayFlex: 1,
|
||||||
fontSize13PX: 1,
|
fontSize13PX: 1,
|
||||||
fontWeightNormal: 1,
|
fontWeightNormal: 1,
|
||||||
colorSubtle: 1,
|
colorSecondary: 1,
|
||||||
underline: hovering,
|
underline: hovering,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NavLink
|
<NavLink
|
||||||
to='/test'
|
to='/test'
|
||||||
className={[styles.default, styles.noUnderline, styles.marginBottom10PX].join(' ')}
|
className={[_s.default, _s.noUnderline, _s.marginBottom10PX].join(' ')}
|
||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
>
|
>
|
||||||
<span className={[styles.default, styles.text, styles.displayFlex, styles.colorBrand, styles.fontSize15PX, styles.fontWeightBold, styles.lineHeight15].join(' ')}>#randomhashtag</span>
|
<span className={[_s.default, _s.text, _s.displayFlex, _s.colorBrand, _s.fontSize15PX, _s.fontWeightBold, _s.lineHeight15].join(' ')}>#randomhashtag</span>
|
||||||
<span className={subtitleClasses}>10,240 Gabs</span>
|
<span className={subtitleClasses}>10,240 Gabs</span>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)
|
)
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
import { NavLink } from 'react-router-dom'
|
|
||||||
import { injectIntl, defineMessages } from 'react-intl'
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
|
||||||
import classNames from 'classnames/bind'
|
|
||||||
import { autoPlayGif, me } from '../initial_state'
|
|
||||||
import { makeGetAccount } from '../selectors'
|
|
||||||
import { shortNumberFormat } from '../utils/numbers'
|
|
||||||
import Avatar from './avatar'
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
|
||||||
gabs: { id: 'account.posts', defaultMessage: 'Gabs' },
|
|
||||||
followers: { id: 'account.followers', defaultMessage: 'Followers' },
|
|
||||||
follows: { id: 'account.follows', defaultMessage: 'Follows' }
|
|
||||||
})
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const getAccount = makeGetAccount()
|
|
||||||
|
|
||||||
return {
|
|
||||||
account: getAccount(state, me),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
|
||||||
@injectIntl
|
|
||||||
class UserPanel extends ImmutablePureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
account: ImmutablePropTypes.map,
|
|
||||||
intl: PropTypes.object.isRequired,
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { account, intl } = this.props
|
|
||||||
const displayNameHtml = { __html: account.get('display_name_html') }
|
|
||||||
|
|
||||||
const statItems = [
|
|
||||||
{
|
|
||||||
to: `/${account.get('acct')}`,
|
|
||||||
title: intl.formatMessage(messages.gabs),
|
|
||||||
value: shortNumberFormat(account.get('statuses_count')),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: `/${account.get('acct')}/followers`,
|
|
||||||
title: intl.formatMessage(messages.followers),
|
|
||||||
value: shortNumberFormat(account.get('followers_count')),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
to: `/${account.get('acct')}/following`,
|
|
||||||
title: intl.formatMessage(messages.follows),
|
|
||||||
value: shortNumberFormat(account.get('following_count')),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className={[styles.default, styles.backgroundWhite, styles.overflowHidden, styles.radiusSmall, styles.marginBottom15PX, styles.borderColorSubtle, styles.border1PX].join(' ')}>
|
|
||||||
<div className={[styles.default].join(' ')}>
|
|
||||||
|
|
||||||
<div className={[styles.default, styles.height122PX].join(' ')}>
|
|
||||||
<img
|
|
||||||
className={[styles.default, styles.image, styles.height122PX, styles.width100PC, styles.objectFitCover].join(' ')}
|
|
||||||
src={autoPlayGif ? account.get('header') : account.get('header_static')}
|
|
||||||
alt=''
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={[styles.default, styles.positionRelative].join(' ')}>
|
|
||||||
<NavLink
|
|
||||||
className={[styles.default, styles.flexRow, styles.paddingVertical10PX, styles.paddingHorizontal15PX, styles.noUnderline].join(' ')}
|
|
||||||
to={`/${account.get('acct')}`}
|
|
||||||
>
|
|
||||||
<div className={[styles.default, styles.marginTopNeg30PX, styles.circle, styles.borderColorWhite, styles.border2PX].join(' ')}>
|
|
||||||
<Avatar account={account} size={62} />
|
|
||||||
</div>
|
|
||||||
<h1 className={[styles.default, styles.marginLeft15PX].join(' ')}>
|
|
||||||
<span
|
|
||||||
className={[styles.default, styles.text, styles.displayBlock, styles.textOverflowEllipsis, styles.fontSize14PX, styles.colorBlack, styles.fontWeightBold].join(' ')}
|
|
||||||
dangerouslySetInnerHTML={displayNameHtml}
|
|
||||||
/>
|
|
||||||
<small className={[styles.default, styles.text, styles.displayBlock, styles.textOverflowEllipsis, styles.fontWeightNormal, styles.fontSize14PX, styles.colorSubtle].join(' ')}>@{account.get('acct')}</small>
|
|
||||||
</h1>
|
|
||||||
</NavLink>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={[styles.default, styles.marginBottom15PX, styles.marginTop5PX, styles.flexRow, styles.paddingHorizontal15PX].join(' ')}>
|
|
||||||
{
|
|
||||||
statItems.map((statItem, i) => (
|
|
||||||
<UserPanelStatItem {...statItem} key={`user-panel-stat-item-${i}`} />
|
|
||||||
))
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class UserPanelStatItem extends PureComponent {
|
|
||||||
static propTypes = {
|
|
||||||
to: PropTypes.string,
|
|
||||||
title: PropTypes.string,
|
|
||||||
value: PropTypes.string,
|
|
||||||
}
|
|
||||||
|
|
||||||
state = {
|
|
||||||
hovering: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseEnter = () => {
|
|
||||||
this.setState({ hovering: true })
|
|
||||||
}
|
|
||||||
|
|
||||||
handleOnMouseLeave = () => {
|
|
||||||
this.setState({ hovering: false })
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { to, title, value } = this.props
|
|
||||||
const { hovering } = this.state
|
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
const titleClasses = cx({
|
|
||||||
default: 1,
|
|
||||||
fontWeightNormal: 1,
|
|
||||||
text: 1,
|
|
||||||
displayFlex: 1,
|
|
||||||
fontSize13PX: 1,
|
|
||||||
colorSubtle: !hovering,
|
|
||||||
colorBlack: hovering,
|
|
||||||
underline: hovering,
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<NavLink
|
|
||||||
to={to}
|
|
||||||
title={`${value} ${title}`}
|
|
||||||
className={[styles.default, styles.flexGrow1, styles.cursorPointer, styles.noUnderline, styles.paddingRight15PX].join(' ')}
|
|
||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={[styles.default, styles.fontWeightBold, styles.text, styles.displayFlex, styles.fontSize19PX, styles.colorBrand].join(' ')}
|
|
||||||
>
|
|
||||||
{value}
|
|
||||||
</span>
|
|
||||||
<strong
|
|
||||||
className={titleClasses}
|
|
||||||
>
|
|
||||||
{title}
|
|
||||||
</strong>
|
|
||||||
</NavLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
44
app/javascript/gabsocial/components/user_stat.js
Normal file
44
app/javascript/gabsocial/components/user_stat.js
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { NavLink } from 'react-router-dom'
|
||||||
|
import Text from './text'
|
||||||
|
|
||||||
|
export default class UserStat extends PureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
to: PropTypes.string,
|
||||||
|
title: PropTypes.string,
|
||||||
|
value: PropTypes.string,
|
||||||
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
hovering: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseEnter = () => {
|
||||||
|
this.setState({ hovering: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleOnMouseLeave = () => {
|
||||||
|
this.setState({ hovering: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { to, title, value } = this.props
|
||||||
|
const { hovering } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={to}
|
||||||
|
title={`${value} ${title}`}
|
||||||
|
className={[_s.default, _s.flexGrow1, _s.cursorPointer, _s.noUnderline, _s.paddingRight15PX].join(' ')}
|
||||||
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
|
>
|
||||||
|
<Text size='large' weight='bold' color='brand'>
|
||||||
|
{value}
|
||||||
|
</Text>
|
||||||
|
<Text size='small' weight='medium' color='secondary' underline={hovering}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -2,7 +2,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { NavLink } from 'react-router-dom';
|
import { NavLink } from 'react-router-dom';
|
||||||
import AvatarOverlay from '../../../../components/avatar_overlay';
|
|
||||||
import DisplayName from '../../../../components/display_name';
|
import DisplayName from '../../../../components/display_name';
|
||||||
import Icon from '../../../../components/icon';
|
import Icon from '../../../../components/icon';
|
||||||
|
|
||||||
@ -13,12 +12,11 @@ export default class MovedNote extends ImmutablePureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
from: ImmutablePropTypes.map.isRequired,
|
|
||||||
to: ImmutablePropTypes.map.isRequired,
|
to: ImmutablePropTypes.map.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { from, to } = this.props;
|
const { to } = this.props;
|
||||||
const displayNameHtml = { __html: from.get('display_name_html') };
|
const displayNameHtml = { __html: from.get('display_name_html') };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -38,7 +36,7 @@ export default class MovedNote extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<NavLink to={`/${this.props.to.get('acct')}`} className='moved-note__display-name'>
|
<NavLink to={`/${this.props.to.get('acct')}`} className='moved-note__display-name'>
|
||||||
<div className='moved-note__display-avatar'>
|
<div className='moved-note__display-avatar'>
|
||||||
<AvatarOverlay account={to} friend={from} />
|
<Avatar account={to} />
|
||||||
</div>
|
</div>
|
||||||
<DisplayName account={to} />
|
<DisplayName account={to} />
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
@ -14,9 +14,9 @@ export default class CharacterCounter extends PureComponent {
|
|||||||
const dashoffset = circumference * (1 - diff)
|
const dashoffset = circumference * (1 - diff)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.marginRight10PX, styles.justifyContentCenter, styles.alignItemsCenter].join(' ')}>
|
<div className={[_s.default, _s.marginRight10PX, _s.justifyContentCenter, _s.alignItemsCenter].join(' ')}>
|
||||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||||
<circle fill='none' cx="16" cy="16" r="12" fill="none" stroke="#e6e6e6" stroke-width="2" />
|
<circle fill='none' cx="16" cy="16" r="12" fill="none" stroke="#e6e6e6" strokeWidth="2" />
|
||||||
<circle style={{
|
<circle style={{
|
||||||
// transform: 'rotate(-90deg)',
|
// transform: 'rotate(-90deg)',
|
||||||
strokeDashoffset: dashoffset,
|
strokeDashoffset: dashoffset,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import classNames from 'classnames/bind'
|
import classNames from 'classnames/bind'
|
||||||
import Icon from '../../../components/icon'
|
import Icon from '../../../components/icon'
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
export default class ComposeExtraButton extends PureComponent {
|
export default class ComposeExtraButton extends PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
@ -25,8 +27,6 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
const { title, disabled, onClick, icon, children } = this.props
|
const { title, disabled, onClick, icon, children } = this.props
|
||||||
const { hovering } = this.state
|
const { hovering } = this.state
|
||||||
|
|
||||||
const cx = classNames.bind(styles)
|
|
||||||
|
|
||||||
const btnClasses = cx({
|
const btnClasses = cx({
|
||||||
default: 1,
|
default: 1,
|
||||||
circle: 1,
|
circle: 1,
|
||||||
@ -44,13 +44,13 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
text: 1,
|
text: 1,
|
||||||
lineHeight15: 1,
|
lineHeight15: 1,
|
||||||
fontSize12PX: 1,
|
fontSize12PX: 1,
|
||||||
fontWeight500: 1,
|
fontWeightMedium: 1,
|
||||||
colorSubtle: 1,
|
colorSecondary: 1,
|
||||||
displayNone: !hovering,
|
displayNone: !hovering,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.marginRight10PX].join(' ')}>
|
<div className={[_s.default, _s.marginRight10PX].join(' ')}>
|
||||||
<button
|
<button
|
||||||
className={btnClasses}
|
className={btnClasses}
|
||||||
title={title}
|
title={title}
|
||||||
@ -59,7 +59,7 @@ export default class ComposeExtraButton extends PureComponent {
|
|||||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||||
>
|
>
|
||||||
<Icon id={icon} width='18px' height='18px' className={styles.fillColorSubtle} />
|
<Icon id={icon} width='18px' height='18px' className={_s.fillcolorSecondary} />
|
||||||
<span className={titleClasses}>
|
<span className={titleClasses}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
|
@ -225,7 +225,7 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={[styles.default, styles.flexGrow1].join(' ')}
|
className={[_s.default, _s.flexGrow1].join(' ')}
|
||||||
ref={this.setForm}
|
ref={this.setForm}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
>
|
>
|
||||||
@ -287,8 +287,8 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
|
|
||||||
{
|
{
|
||||||
/* !condensed && */
|
/* !condensed && */
|
||||||
<div className={[styles.default, styles.flexRow, styles.marginTop10PX].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.marginTop10PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.flexRow, styles.marginRightAuto].join(' ')}>
|
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
||||||
<UploadButton />
|
<UploadButton />
|
||||||
{
|
{
|
||||||
!edit && <PollButton />
|
!edit && <PollButton />
|
||||||
@ -299,11 +299,12 @@ class ComposeForm extends ImmutablePureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<CharacterCounter max={maxPostCharacterCount} text={text} />
|
<CharacterCounter max={maxPostCharacterCount} text={text} />
|
||||||
<Button
|
<Button
|
||||||
className={[styles.fontSize15PX, styles.paddingHorizontal15PX].join(' ')}
|
className={[_s.fontSize15PX, _s.paddingHorizontal15PX].join(' ')}
|
||||||
text={intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
|
|
||||||
onClick={this.handleSubmit}
|
onClick={this.handleSubmit}
|
||||||
disabled={disabledButton}
|
disabled={disabledButton}
|
||||||
/>
|
>
|
||||||
|
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ class UploadButton extends ImmutablePureComponent {
|
|||||||
icon='media'
|
icon='media'
|
||||||
>
|
>
|
||||||
<label>
|
<label>
|
||||||
<span className={styles.displayNone}>{intl.formatMessage(messages.upload)}</span>
|
<span className={_s.displayNone}>{intl.formatMessage(messages.upload)}</span>
|
||||||
<input
|
<input
|
||||||
key={resetFileKey}
|
key={resetFileKey}
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
@ -76,7 +76,7 @@ class UploadButton extends ImmutablePureComponent {
|
|||||||
accept={acceptContentTypes.toArray().join(',')}
|
accept={acceptContentTypes.toArray().join(',')}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={styles.displayNone}
|
className={_s.displayNone}
|
||||||
multiple
|
multiple
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,18 +1,15 @@
|
|||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import { expandHomeTimeline } from '../../actions/timelines';
|
import { expandHomeTimeline } from '../../actions/timelines'
|
||||||
import StatusListContainer from '../../containers/status_list_container';
|
import StatusListContainer from '../../containers/status_list_container'
|
||||||
import ColumnSettings from './components/column_settings';
|
|
||||||
import Column from '../../components/column';
|
|
||||||
import { HomeColumnHeader } from '../../components/column_header';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.home', defaultMessage: 'Home' },
|
title: { id: 'column.home', defaultMessage: 'Home' },
|
||||||
});
|
empty: { id: 'empty_column.home', defaultMessage: 'Your home timeline is empty. Start following other users to recieve their content here.' },
|
||||||
|
})
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
hasUnread: state.getIn(['timelines', 'home', 'unread']) > 0,
|
|
||||||
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
isPartial: state.getIn(['timelines', 'home', 'isPartial']),
|
||||||
});
|
})
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
@ -21,60 +18,57 @@ class HomeTimeline extends PureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
hasUnread: PropTypes.bool,
|
|
||||||
isPartial: PropTypes.bool,
|
isPartial: PropTypes.bool,
|
||||||
};
|
}
|
||||||
|
|
||||||
handleLoadMore = maxId => {
|
handleLoadMore = maxId => {
|
||||||
this.props.dispatch(expandHomeTimeline({ maxId }));
|
this.props.dispatch(expandHomeTimeline({ maxId }))
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
this._checkIfReloadNeeded(false, this.props.isPartial);
|
this._checkIfReloadNeeded(false, this.props.isPartial)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate (prevProps) {
|
componentDidUpdate (prevProps) {
|
||||||
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial);
|
this._checkIfReloadNeeded(prevProps.isPartial, this.props.isPartial)
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount () {
|
componentWillUnmount () {
|
||||||
this._stopPolling();
|
this._stopPolling()
|
||||||
}
|
}
|
||||||
|
|
||||||
_checkIfReloadNeeded (wasPartial, isPartial) {
|
_checkIfReloadNeeded (wasPartial, isPartial) {
|
||||||
const { dispatch } = this.props;
|
const { dispatch } = this.props
|
||||||
|
|
||||||
if (wasPartial === isPartial) {
|
if (!wasPartial && isPartial) {
|
||||||
return;
|
|
||||||
} else if (!wasPartial && isPartial) {
|
|
||||||
this.polling = setInterval(() => {
|
this.polling = setInterval(() => {
|
||||||
dispatch(expandHomeTimeline());
|
dispatch(expandHomeTimeline())
|
||||||
}, 3000);
|
}, 3000)
|
||||||
} else if (wasPartial && !isPartial) {
|
} else if (wasPartial && !isPartial) {
|
||||||
this._stopPolling();
|
this._stopPolling()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_stopPolling () {
|
_stopPolling () {
|
||||||
if (this.polling) {
|
if (this.polling) {
|
||||||
clearInterval(this.polling);
|
clearInterval(this.polling)
|
||||||
this.polling = null;
|
this.polling = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { intl, hasUnread } = this.props;
|
const { intl } = this.props
|
||||||
|
|
||||||
|
const emptyMessage = intl.formatMessage(messages.empty)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column heading={intl.formatMessage(messages.title)}>
|
<StatusListContainer
|
||||||
<StatusListContainer
|
scrollKey='home_timeline'
|
||||||
scrollKey='home_timeline'
|
onLoadMore={this.handleLoadMore}
|
||||||
onLoadMore={this.handleLoadMore}
|
timelineId='home'
|
||||||
timelineId='home'
|
emptyMessage={emptyMessage}
|
||||||
emptyMessage={<FormattedMessage id='empty_column.home' defaultMessage='Your home timeline is empty. Start following other users to recieve their content here.'/>}
|
/>
|
||||||
/>
|
)
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,6 @@ import List from './components/list';
|
|||||||
import Account from '../../components/account';
|
import Account from '../../components/account';
|
||||||
import IconButton from '../../components/icon_button';
|
import IconButton from '../../components/icon_button';
|
||||||
import NewListForm from '../lists/components/new_list_form';
|
import NewListForm from '../lists/components/new_list_form';
|
||||||
import ColumnSubheading from '../../components/column_subheading/column_subheading';
|
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
if (!lists) {
|
if (!lists) {
|
||||||
@ -78,12 +77,10 @@ class ListAdder extends ImmutablePureComponent {
|
|||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.add)} />
|
|
||||||
<NewListForm />
|
<NewListForm />
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
|
||||||
<div className='list-adder__lists'>
|
<div className='list-adder__lists'>
|
||||||
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -5,7 +5,6 @@ import { setupListEditor, resetListEditor } from '../../actions/lists';
|
|||||||
import Account from './components/account';
|
import Account from './components/account';
|
||||||
import ListEditorSearch from './components/list_editor_search';
|
import ListEditorSearch from './components/list_editor_search';
|
||||||
import EditListForm from './components/edit_list_form/edit_list_form';
|
import EditListForm from './components/edit_list_form/edit_list_form';
|
||||||
import ColumnSubheading from '../../components/column_subheading';
|
|
||||||
import IconButton from '../../components/icon_button';
|
import IconButton from '../../components/icon_button';
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
@ -66,14 +65,12 @@ class ListEditor extends ImmutablePureComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className='compose-modal__content'>
|
<div className='compose-modal__content'>
|
||||||
<div className='list-editor'>
|
<div className='list-editor'>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.changeTitle)} />
|
|
||||||
<EditListForm />
|
<EditListForm />
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
{
|
{
|
||||||
accountIds.size > 0 &&
|
accountIds.size > 0 &&
|
||||||
<div>
|
<div>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.removeFromList)} />
|
|
||||||
<div className='list-editor__accounts'>
|
<div className='list-editor__accounts'>
|
||||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +78,6 @@ class ListEditor extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.addToList)} />
|
|
||||||
<ListEditorSearch />
|
<ListEditorSearch />
|
||||||
<div className='list-editor__accounts'>
|
<div className='list-editor__accounts'>
|
||||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||||
|
@ -6,11 +6,8 @@ import { expandListTimeline } from '../../actions/timelines';
|
|||||||
import { fetchList, deleteList } from '../../actions/lists';
|
import { fetchList, deleteList } from '../../actions/lists';
|
||||||
import { openModal } from '../../actions/modal';
|
import { openModal } from '../../actions/modal';
|
||||||
import StatusListContainer from '../../containers/status_list_container';
|
import StatusListContainer from '../../containers/status_list_container';
|
||||||
import Column from '../../components/column';
|
|
||||||
import ColumnIndicator from '../../components/column_indicator';
|
import ColumnIndicator from '../../components/column_indicator';
|
||||||
import { HomeColumnHeader } from '../../components/column_header';
|
|
||||||
import Button from '../../components/button';
|
import Button from '../../components/button';
|
||||||
import ColumnHeaderSettingButton from '../../components/column_header_setting_button';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
deleteMessage: { id: 'confirmations.delete_list.message', defaultMessage: 'Are you sure you want to permanently delete this list?' },
|
||||||
@ -19,7 +16,6 @@ const messages = defineMessages({
|
|||||||
|
|
||||||
const mapStateToProps = (state, props) => ({
|
const mapStateToProps = (state, props) => ({
|
||||||
list: state.getIn(['lists', props.params.id]),
|
list: state.getIn(['lists', props.params.id]),
|
||||||
hasUnread: state.getIn(['timelines', `list:${props.params.id}`, 'unread']) > 0,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
@ -33,7 +29,6 @@ class ListTimeline extends ImmutablePureComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
hasUnread: PropTypes.bool,
|
|
||||||
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
list: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
};
|
||||||
@ -93,7 +88,7 @@ class ListTimeline extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { hasUnread, list } = this.props;
|
const { list } = this.props;
|
||||||
const { id } = this.props.params;
|
const { id } = this.props.params;
|
||||||
const title = list ? list.get('title') : id;
|
const title = list ? list.get('title') : id;
|
||||||
|
|
||||||
@ -116,38 +111,12 @@ class ListTimeline extends ImmutablePureComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column heading={title}>
|
<StatusListContainer
|
||||||
<HomeColumnHeader activeItem='lists' activeSubItem={id} active={hasUnread}>
|
scrollKey='list_timeline'
|
||||||
<div>
|
timelineId={`list:${id}`}
|
||||||
<ColumnHeaderSettingButton
|
onLoadMore={this.handleLoadMore}
|
||||||
onClick={this.handleEditClick}
|
emptyMessage={emptyMessage}
|
||||||
icon='pencil'
|
/>
|
||||||
title={<FormattedMessage id='lists.edit' defaultMessage='Edit list' />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ColumnHeaderSettingButton
|
|
||||||
onClick={this.handleDeleteClick}
|
|
||||||
icon='trash'
|
|
||||||
title={<FormattedMessage id='lists.delete' defaultMessage='Delete list' />}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<ColumnHeaderSettingButton
|
|
||||||
to='/lists'
|
|
||||||
icon='arrow-right'
|
|
||||||
title={<FormattedMessage id='lists.view_all' defaultMessage='View all lists' />}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</HomeColumnHeader>
|
|
||||||
|
|
||||||
<StatusListContainer
|
|
||||||
scrollKey='list_timeline'
|
|
||||||
timelineId={`list:${id}`}
|
|
||||||
onLoadMore={this.handleLoadMore}
|
|
||||||
emptyMessage={emptyMessage}
|
|
||||||
/>
|
|
||||||
</Column>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,77 +1,118 @@
|
|||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
import { createSelector } from 'reselect';
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
import { NavLink } from 'react-router-dom'
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import { createSelector } from 'reselect'
|
||||||
import { fetchLists } from '../../actions/lists';
|
import { defineMessages, injectIntl } from 'react-intl'
|
||||||
import ColumnIndicator from '../../components/column_indicator';
|
import classNames from 'classnames/bind'
|
||||||
import Column from '../../components/column';
|
import { fetchLists } from '../../actions/lists'
|
||||||
import ColumnLink from '../../components/column_link';
|
import ColumnIndicator from '../../components/column_indicator'
|
||||||
import ColumnSubheading from '../../components/column_subheading';
|
import ScrollableList from '../../components/scrollable_list'
|
||||||
import NewListForm from './components/new_list_form';
|
import Icon from '../../components/icon'
|
||||||
import ScrollableList from '../../components/scrollable_list';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
heading: { id: 'column.lists', defaultMessage: 'Lists' },
|
|
||||||
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
|
||||||
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
|
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
|
||||||
});
|
empty: { id: 'empty_column.lists', defaultMessage: 'You don\'t have any lists yet. When you create one, it will show up here.' },
|
||||||
|
})
|
||||||
|
|
||||||
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
|
||||||
if (!lists) {
|
if (!lists) return lists
|
||||||
return lists;
|
|
||||||
}
|
|
||||||
|
|
||||||
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')));
|
return lists.toList().filter(item => !!item).sort((a, b) => a.get('title').localeCompare(b.get('title')))
|
||||||
});
|
})
|
||||||
|
|
||||||
const mapStateToProps = state => ({
|
const mapStateToProps = state => ({
|
||||||
lists: getOrderedLists(state),
|
lists: getOrderedLists(state),
|
||||||
});
|
})
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
onFetchLists() {
|
||||||
|
return dispatch(fetchLists())
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const cx = classNames.bind(_s)
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps, mapDispatchToProps)
|
||||||
@injectIntl
|
@injectIntl
|
||||||
class Lists extends ImmutablePureComponent {
|
class Lists extends ImmutablePureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
params: PropTypes.object.isRequired,
|
params: PropTypes.object.isRequired,
|
||||||
dispatch: PropTypes.func.isRequired,
|
onFetchLists: PropTypes.func.isRequired,
|
||||||
lists: ImmutablePropTypes.list,
|
lists: ImmutablePropTypes.list,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
};
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
fetched: false,
|
||||||
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this.props.dispatch(fetchLists());
|
this.props.onFetchLists()
|
||||||
|
.then(() => this.setState({ fetched: true }))
|
||||||
|
.catch(() => this.setState({ fetched: true }))
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, lists } = this.props;
|
const { intl, lists } = this.props
|
||||||
|
const { fetched } = this.state
|
||||||
|
|
||||||
if (!lists) {
|
if (lists.size === 0 && !fetched) {
|
||||||
return (<ColumnIndicator type='loading' />);
|
return <ColumnIndicator type='loading' />
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const emptyMessage = intl.formatMessage(messages.empty)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Column icon='list-ul' heading={intl.formatMessage(messages.heading)} backBtn='slim'>
|
<ScrollableList
|
||||||
<br />
|
scrollKey='lists'
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.add)} />
|
emptyMessage={emptyMessage}
|
||||||
<NewListForm />
|
>
|
||||||
<br />
|
<div className={[_s.default, _s.backgroundWhite, _s.radiusSmall, _s.overflowHidden, _s.border1PX, _s.bordercolorSecondary].join(' ')}>
|
||||||
<ColumnSubheading text={intl.formatMessage(messages.subheading)} />
|
{
|
||||||
<ScrollableList
|
lists.map((list, i) => {
|
||||||
scrollKey='lists'
|
const isLast = lists.length - 1 === i
|
||||||
emptyMessage={<FormattedMessage id='empty_column.lists' defaultMessage="You don't have any lists yet. When you create one, it will show up here." />}
|
return (
|
||||||
>
|
<ListItem key={list.get('id')} list={list} isLast={isLast} />
|
||||||
{lists.map(list =>
|
)
|
||||||
<ColumnLink
|
})
|
||||||
key={list.get('id')}
|
}
|
||||||
to={`/list/${list.get('id')}`}
|
</div>
|
||||||
icon='list-ul'
|
</ScrollableList>
|
||||||
text={list.get('title')}
|
)
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</ScrollableList>
|
|
||||||
</Column>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ListItem extends ImmutablePureComponent {
|
||||||
|
static propTypes = {
|
||||||
|
isLast: PropTypes.bool,
|
||||||
|
list: ImmutablePropTypes.map,
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { list, isLast } = this.props
|
||||||
|
|
||||||
|
const containerClasses = cx({
|
||||||
|
default: 1,
|
||||||
|
cursorPointer: 1,
|
||||||
|
noUnderline: 1,
|
||||||
|
paddingHorizontal15PX: 1,
|
||||||
|
paddingVertical15PX: 1,
|
||||||
|
flexRow: 1,
|
||||||
|
alignItemsCenter: 1,
|
||||||
|
backgroundSubtle_onHover: 1,
|
||||||
|
bordercolorSecondary: !isLast,
|
||||||
|
borderBottom1PX: !isLast,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavLink to={`/list/${list.get('id')}`} className={containerClasses} >
|
||||||
|
<span className={[_s.default, _s.text, _s.colorPrimary, _s.fontSize14PX].join(' ')}>
|
||||||
|
{list.get('title')}
|
||||||
|
</span>
|
||||||
|
<Icon id='angle-right' className={[_s.marginLeftAuto, _s.fillColorBlack].join(' ')} width='10px' height='10px' />
|
||||||
|
</NavLink>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -124,7 +124,7 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
className={[styles.default, styles.backgroundColorSubtle3, styles.positionAbsolute, styles.top0, styles.right0, styles.bottom0, styles.left0, styles.statusCardVideo].join(' ')}
|
className={[_s.default, _s.backgroundcolorSecondary3, _s.positionAbsolute, _s.top0, _s.right0, _s.bottom0, _s.left0, _s.statusCardVideo].join(' ')}
|
||||||
dangerouslySetInnerHTML={content}
|
dangerouslySetInnerHTML={content}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -145,7 +145,7 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
const title = interactive ?
|
const title = interactive ?
|
||||||
(
|
(
|
||||||
<a
|
<a
|
||||||
className={[styles.default, styles.displayFlex, styles.text, styles.noUnderline, styles.overflowWrapBreakWord, styles.colorBlack, styles.fontSize15PX, styles.fontWeight500].join(' ')}
|
className={[_s.default, _s.displayFlex, _s.text, _s.noUnderline, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}
|
||||||
href={card.get('url')}
|
href={card.get('url')}
|
||||||
title={card.get('title')}
|
title={card.get('title')}
|
||||||
rel='noopener'
|
rel='noopener'
|
||||||
@ -155,19 +155,19 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<span className={[styles.default, styles.displayFlex, styles.text, styles.overflowWrapBreakWord, styles.colorBlack, styles.fontSize15PX, styles.fontWeight500].join(' ')}>
|
<span className={[_s.default, _s.displayFlex, _s.text, _s.overflowWrapBreakWord, _s.colorPrimary, _s.fontSize15PX, _s.fontWeightMedium].join(' ')}>
|
||||||
{card.get('title')}
|
{card.get('title')}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
|
|
||||||
const description = (
|
const description = (
|
||||||
<div className={[styles.default, styles.flexNormal, styles.paddingHorizontal10PX, styles.paddingVertical10PX, styles.borderColorSubtle, styles.borderLeft1PX].join(' ')}>
|
<div className={[_s.default, _s.flexNormal, _s.paddingHorizontal10PX, _s.paddingVertical10PX, _s.bordercolorSecondary, _s.borderLeft1PX].join(' ')}>
|
||||||
{title}
|
{title}
|
||||||
<p className={[styles.default, styles.displayFlex, styles.text, styles.marginVertical5PX, styles.overflowWrapBreakWord, styles.colorSubtle, styles.fontSize13PX, styles.fontWeightNormal].join(' ')}>
|
<p className={[_s.default, _s.displayFlex, _s.text, _s.marginVertical5PX, _s.overflowWrapBreakWord, _s.colorSecondary, _s.fontSize13PX, _s.fontWeightNormal].join(' ')}>
|
||||||
{trim(card.get('description') || '', maxDescription)}
|
{trim(card.get('description') || '', maxDescription)}
|
||||||
</p>
|
</p>
|
||||||
<span className={[styles.default, styles.marginTopAuto, styles.flexRow, styles.alignItemsCenter, styles.colorSubtle, styles.text, styles.displayFlex, styles.textOverflowEllipsis, styles.fontSize13PX].join(' ')}>
|
<span className={[_s.default, _s.marginTopAuto, _s.flexRow, _s.alignItemsCenter, _s.colorSecondary, _s.text, _s.displayFlex, _s.textOverflowEllipsis, _s.fontSize13PX].join(' ')}>
|
||||||
<Icon id='link' width='12px' height='12px' className={[styles.fillColorSubtle, styles.marginRight5PX].join(' ')} fixedWidth />
|
<Icon id='link' width='12px' height='12px' className={[_s.fillcolorSecondary, _s.marginRight5PX].join(' ')} fixedWidth />
|
||||||
{provider}
|
{provider}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -175,9 +175,9 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
|
|
||||||
let embed = ''
|
let embed = ''
|
||||||
let thumbnail = interactive ?
|
let thumbnail = interactive ?
|
||||||
<img src={cardImg} className={[styles.default, styles.objectFitCover, styles.positionAbsolute, styles.width100PC, styles.height100PC, styles.top0, styles.right0, styles.bottom0, styles.left0].join(' ')} />
|
<img src={cardImg} className={[_s.default, _s.objectFitCover, _s.positionAbsolute, _s.width100PC, _s.height100PC, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')} />
|
||||||
:
|
:
|
||||||
<img src={cardImg} className={[styles.default, styles.objectFitCover, styles.width400PX, styles.height260PX].join(' ')} />
|
<img src={cardImg} className={[_s.default, _s.objectFitCover, _s.width400PX, _s.height260PX].join(' ')} />
|
||||||
|
|
||||||
if (interactive) {
|
if (interactive) {
|
||||||
if (embedded) {
|
if (embedded) {
|
||||||
@ -191,19 +191,19 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.width100PC, styles.paddingHorizontal10PX].join(' ')}>
|
<div className={[_s.default, _s.width100PC, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<div className={[styles.default, styles.overflowHidden, styles.width100PC, styles.borderColorSubtle2, styles.border1PX, styles.radiusSmall].join(' ')}>
|
<div className={[_s.default, _s.overflowHidden, _s.width100PC, _s.bordercolorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}>
|
||||||
<div className={[styles.default, styles.width100PC].join(' ')}>
|
<div className={[_s.default, _s.width100PC].join(' ')}>
|
||||||
<div className={[styles.default, styles.width100PC, styles.paddingTop5625PC].join(' ')}>
|
<div className={[_s.default, _s.width100PC, _s.paddingTop5625PC].join(' ')}>
|
||||||
{ !!embed && embed}
|
{ !!embed && embed}
|
||||||
{ !embed && thumbnail}
|
{ !embed && thumbnail}
|
||||||
{ !embed &&
|
{ !embed &&
|
||||||
<div className={[styles.default, styles.positionAbsolute, styles.top0, styles.right0, styles.left0, styles.bottom0, styles.alignItemsCenter, styles.justifyContentCenter].join(' ')}>
|
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.right0, _s.left0, _s.bottom0, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
<button
|
<button
|
||||||
className={[styles.default, styles.cursorPointer, styles.backgroundColorOpaque, styles.radiusSmall, styles.paddingVertical15PX, styles.paddingHorizontal15PX].join(' ')}
|
className={[_s.default, _s.cursorPointer, _s.backgroundColorOpaque, _s.radiusSmall, _s.paddingVertical15PX, _s.paddingHorizontal15PX].join(' ')}
|
||||||
onClick={this.handleEmbedClick}
|
onClick={this.handleEmbedClick}
|
||||||
>
|
>
|
||||||
<Icon id={iconVariant} className={[styles.fillColorWhite].join(' ')}/>
|
<Icon id={iconVariant} className={[_s.fillColorWhite].join(' ')}/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -215,23 +215,23 @@ export default class Card extends ImmutablePureComponent {
|
|||||||
)
|
)
|
||||||
} else if (cardImg) {
|
} else if (cardImg) {
|
||||||
embed = (
|
embed = (
|
||||||
<div className={[styles.default].join(' ')}>
|
<div className={[_s.default].join(' ')}>
|
||||||
{thumbnail}
|
{thumbnail}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
embed = (
|
embed = (
|
||||||
<div className={[styles.default, styles.paddingVertical15PX, styles.paddingHorizontal15PX, styles.width72PX, styles.alignItemsCenter, styles.justifyContentCenter].join(' ')}>
|
<div className={[_s.default, _s.paddingVertical15PX, _s.paddingHorizontal15PX, _s.width72PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
|
||||||
<Icon id='file-text' width='22px' height='22px' className={styles.fillColorSubtle} />
|
<Icon id='file-text' width='22px' height='22px' className={_s.fillcolorSecondary} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={[styles.default, styles.width100PC, styles.paddingHorizontal10PX].join(' ')}>
|
<div className={[_s.default, _s.width100PC, _s.paddingHorizontal10PX].join(' ')}>
|
||||||
<a
|
<a
|
||||||
href={card.get('url')}
|
href={card.get('url')}
|
||||||
className={[styles.default, styles.cursorPointer, styles.flexRow, styles.overflowHidden, styles.noUnderline, styles.width100PC, styles.borderColorSubtle2, styles.border1PX, styles.radiusSmall].join(' ')}
|
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.bordercolorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
|
||||||
rel='noopener'
|
rel='noopener'
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
>
|
>
|
||||||
|
@ -30,6 +30,8 @@ import ProfilePage from '../../pages/profile_page'
|
|||||||
// import SearchPage from '../../pages/search_page';
|
// import SearchPage from '../../pages/search_page';
|
||||||
import HomePage from '../../pages/home_page'
|
import HomePage from '../../pages/home_page'
|
||||||
import NotificationsPage from '../../pages/notifications_page'
|
import NotificationsPage from '../../pages/notifications_page'
|
||||||
|
import ListPage from '../../pages/list_page'
|
||||||
|
import ListsPage from '../../pages/lists_page'
|
||||||
// import GroupSidebarPanel from '../groups/sidebar_panel';
|
// import GroupSidebarPanel from '../groups/sidebar_panel';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -57,8 +59,8 @@ import {
|
|||||||
// Explore,
|
// Explore,
|
||||||
// Groups,
|
// Groups,
|
||||||
// GroupTimeline,
|
// GroupTimeline,
|
||||||
// ListTimeline,
|
ListTimeline,
|
||||||
// Lists,
|
Lists,
|
||||||
// GroupMembers,
|
// GroupMembers,
|
||||||
// GroupRemovedAccounts,
|
// GroupRemovedAccounts,
|
||||||
// GroupCreate,
|
// GroupCreate,
|
||||||
@ -192,11 +194,11 @@ class SwitchingColumnsArea extends PureComponent {
|
|||||||
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
|
<WrappedRoute path='/groups/:id' page={GroupPage} component={GroupTimeline} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
|
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
|
||||||
|
*/}
|
||||||
|
<WrappedRoute path='/lists' page={ListsPage} component={Lists} content={children} />
|
||||||
|
<WrappedRoute path='/list/:id' page={ListPage} component={ListTimeline} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/lists' layout={LAYOUT.DEFAULT} component={Lists} content={children} />
|
<WrappedRoute path='/notifications' page={NotificationsPage} component={Notifications} content={children} />
|
||||||
<WrappedRoute path='/list/:id' page={HomePage} component={ListTimeline} content={children} />
|
|
||||||
*/}
|
|
||||||
<WrappedRoute path='/notifications' layout={LAYOUT.DEFAULT} page={NotificationsPage} component={Notifications} content={children} />
|
|
||||||
{/*
|
{/*
|
||||||
<WrappedRoute path='/search' exact publicRoute page={SearchPage} component={Search} content={children} />
|
<WrappedRoute path='/search' exact publicRoute page={SearchPage} component={Search} content={children} />
|
||||||
<WrappedRoute path='/search/people' exact page={SearchPage} component={Search} content={children} />
|
<WrappedRoute path='/search/people' exact page={SearchPage} component={Search} content={children} />
|
||||||
|
@ -3,21 +3,37 @@ import GroupSidebarPanel from '../components/panel/groups_panel'
|
|||||||
import LinkFooter from '../components/link_footer'
|
import LinkFooter from '../components/link_footer'
|
||||||
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
||||||
import ProgressPanel from '../components/panel/progress_panel'
|
import ProgressPanel from '../components/panel/progress_panel'
|
||||||
import UserPanel from '../components/user_panel'
|
import UserPanel from '../components/panel/user_panel'
|
||||||
|
import TrendsPanel from '../components/panel/trends_panel'
|
||||||
|
import HashtagsPanel from '../components/panel/hashtags_panel'
|
||||||
import DefaultLayout from '../components/layouts/default_layout'
|
import DefaultLayout from '../components/layouts/default_layout'
|
||||||
import TimelineComposeBlock from '../components/timeline_compose_block'
|
import TimelineComposeBlock from '../components/timeline_compose_block'
|
||||||
|
import Divider from '../components/divider'
|
||||||
|
|
||||||
export default class HomePage extends PureComponent {
|
export default class HomePage extends PureComponent {
|
||||||
|
|
||||||
|
handleEditHomeTimeline () {
|
||||||
|
console.log("handleEditHomeTimeline")
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { children } = this.props
|
const { children } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DefaultLayout
|
<DefaultLayout
|
||||||
title='Home'
|
title='Home'
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
icon: 'ellipsis',
|
||||||
|
onClick: this.handleEditHomeTimeline
|
||||||
|
},
|
||||||
|
]}
|
||||||
layout={(
|
layout={(
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<UserPanel />
|
<UserPanel />
|
||||||
<ProgressPanel />
|
<ProgressPanel />
|
||||||
|
<TrendsPanel />
|
||||||
|
<HashtagsPanel />
|
||||||
<WhoToFollowPanel />
|
<WhoToFollowPanel />
|
||||||
<GroupSidebarPanel />
|
<GroupSidebarPanel />
|
||||||
<LinkFooter />
|
<LinkFooter />
|
||||||
@ -25,7 +41,7 @@ export default class HomePage extends PureComponent {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<TimelineComposeBlock autoFocus={false} shouldCondense />
|
<TimelineComposeBlock autoFocus={false} shouldCondense />
|
||||||
<div className={[styles.default, styles.borderBottom1PX, styles.borderColorSubtle2, styles.marginBottom15PX, styles.width100PC].join(' ')} />
|
<Divider />
|
||||||
{children}
|
{children}
|
||||||
</DefaultLayout>
|
</DefaultLayout>
|
||||||
)
|
)
|
||||||
|
49
app/javascript/gabsocial/pages/list_page.js
Normal file
49
app/javascript/gabsocial/pages/list_page.js
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import { Fragment } from 'react'
|
||||||
|
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||||
|
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||||
|
import LinkFooter from '../components/link_footer'
|
||||||
|
import DefaultLayout from '../components/layouts/default_layout'
|
||||||
|
import ListDetailsPanel from '../components/panel/list_details_panel'
|
||||||
|
|
||||||
|
const mapStateToProps = (state, props) => ({
|
||||||
|
list: state.getIn(['lists', props.params.id]),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
|
class ListPage extends ImmutablePureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
list: ImmutablePropTypes.map,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleEditListTimeline () {
|
||||||
|
console.log("handleEditListTimeline")
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children, list } = this.props
|
||||||
|
|
||||||
|
const title = list ? list.get('title') : ''
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultLayout
|
||||||
|
title={title}
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
icon: 'ellipsis',
|
||||||
|
onClick: this.handleEditListTimeline
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
layout={(
|
||||||
|
<Fragment>
|
||||||
|
<ListDetailsPanel />
|
||||||
|
<LinkFooter />
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
showBackBtn
|
||||||
|
>
|
||||||
|
{ children }
|
||||||
|
</DefaultLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
46
app/javascript/gabsocial/pages/lists_page.js
Normal file
46
app/javascript/gabsocial/pages/lists_page.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Fragment } from 'react'
|
||||||
|
import LinkFooter from '../components/link_footer'
|
||||||
|
import WhoToFollowPanel from '../components/panel/who_to_follow_panel'
|
||||||
|
import TrendsPanel from '../components/panel/trends_panel'
|
||||||
|
import DefaultLayout from '../components/layouts/default_layout'
|
||||||
|
|
||||||
|
export default class ListsPage extends PureComponent {
|
||||||
|
|
||||||
|
handleClickNewList () {
|
||||||
|
console.log("handleClickNewList")
|
||||||
|
}
|
||||||
|
|
||||||
|
handleClickEditLists () {
|
||||||
|
console.log("handleClickEditLists")
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { children } = this.props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DefaultLayout
|
||||||
|
title='Lists'
|
||||||
|
actions={[
|
||||||
|
{
|
||||||
|
icon: 'subtract',
|
||||||
|
onClick: this.handleClickEditLists
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: 'add',
|
||||||
|
onClick: this.handleClickNewList
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
layout={(
|
||||||
|
<Fragment>
|
||||||
|
<TrendsPanel />
|
||||||
|
<WhoToFollowPanel />
|
||||||
|
<LinkFooter />
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
showBackBtn
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</DefaultLayout>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
TRENDS_FETCH_REQUEST,
|
HASHTAGS_FETCH_REQUEST,
|
||||||
TRENDS_FETCH_SUCCESS,
|
HASHTAGS_FETCH_SUCCESS,
|
||||||
TRENDS_FETCH_FAIL,
|
HASHTAGS_FETCH_FAIL,
|
||||||
} from '../actions/trends';
|
} from '../actions/hashtags';
|
||||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||||
|
|
||||||
const initialState = ImmutableMap({
|
const initialState = ImmutableMap({
|
||||||
@ -10,16 +10,16 @@ const initialState = ImmutableMap({
|
|||||||
isLoading: false,
|
isLoading: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function trendsReducer(state = initialState, action) {
|
export default function hashtagsReducer(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case TRENDS_FETCH_REQUEST:
|
case HASHTAGS_FETCH_REQUEST:
|
||||||
return state.set('isLoading', true);
|
return state.set('isLoading', true);
|
||||||
case TRENDS_FETCH_SUCCESS:
|
case HASHTAGS_FETCH_SUCCESS:
|
||||||
return state.withMutations(map => {
|
return state.withMutations(map => {
|
||||||
map.set('items', fromJS(action.tags.map((x => x))))
|
map.set('items', fromJS(action.tags.map((x => x))))
|
||||||
map.set('isLoading', false);
|
map.set('isLoading', false);
|
||||||
});
|
});
|
||||||
case TRENDS_FETCH_FAIL:
|
case HASHTAGS_FETCH_FAIL:
|
||||||
return state.set('isLoading', false);
|
return state.set('isLoading', false);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
@ -31,7 +31,7 @@ import conversations from './conversations';
|
|||||||
import suggestions from './suggestions';
|
import suggestions from './suggestions';
|
||||||
import polls from './polls';
|
import polls from './polls';
|
||||||
import identity_proofs from './identity_proofs';
|
import identity_proofs from './identity_proofs';
|
||||||
import trends from './trends';
|
import hashtags from './hashtags';
|
||||||
import groups from './groups';
|
import groups from './groups';
|
||||||
import group_relationships from './group_relationships';
|
import group_relationships from './group_relationships';
|
||||||
import group_lists from './group_lists';
|
import group_lists from './group_lists';
|
||||||
@ -72,7 +72,7 @@ const reducers = {
|
|||||||
conversations,
|
conversations,
|
||||||
suggestions,
|
suggestions,
|
||||||
polls,
|
polls,
|
||||||
trends,
|
hashtags,
|
||||||
groups,
|
groups,
|
||||||
group_relationships,
|
group_relationships,
|
||||||
group_lists,
|
group_lists,
|
||||||
|
@ -1,29 +1,57 @@
|
|||||||
'use strict';
|
'use strict'
|
||||||
|
|
||||||
import detectPassiveEvents from 'detect-passive-events';
|
import detectPassiveEvents from 'detect-passive-events'
|
||||||
|
|
||||||
const LAYOUT_BREAKPOINT = 630;
|
const BREAKPOINT_EXTRA_LARGE = 1480
|
||||||
|
const BREAKPOINT_LARGE = 1280
|
||||||
|
const BREAKPOINT_MEDIUM = 1160
|
||||||
|
const BREAKPOINT_SMALL = 1080
|
||||||
|
const BREAKPOINT_EXTRA_SMALL = 992
|
||||||
|
|
||||||
export function isMobile(width) {
|
export function breakpointExtraLarge(width) {
|
||||||
return width <= LAYOUT_BREAKPOINT;
|
return width > BREAKPOINT_EXTRA_LARGE
|
||||||
};
|
|
||||||
|
|
||||||
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
||||||
|
|
||||||
let userTouching = false;
|
|
||||||
let listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false;
|
|
||||||
|
|
||||||
function touchListener() {
|
|
||||||
userTouching = true;
|
|
||||||
window.removeEventListener('touchstart', touchListener, listenerOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('touchstart', touchListener, listenerOptions);
|
export function breakpointLarge(width) {
|
||||||
|
return width > BREAKPOINT_MEDIUM && width < BREAKPOINT_LARGE
|
||||||
|
}
|
||||||
|
|
||||||
|
export function breakpointMedium(width) {
|
||||||
|
return width > BREAKPOINT_SMALL && width < BREAKPOINT_MEDIUM
|
||||||
|
}
|
||||||
|
|
||||||
|
export function breakpointSmall(width) {
|
||||||
|
return width > BREAKPOINT_EXTRA_SMALL && width < BREAKPOINT_SMALL
|
||||||
|
}
|
||||||
|
|
||||||
|
export function breakpointExtraSmall(width) {
|
||||||
|
return width < BREAKPOINT_EXTRA_SMALL
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
|
||||||
|
const LAYOUT_BREAKPOINT = 630
|
||||||
|
|
||||||
|
export function isMobile(width) {
|
||||||
|
return width <= LAYOUT_BREAKPOINT
|
||||||
|
}
|
||||||
|
|
||||||
|
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
|
||||||
|
|
||||||
|
let userTouching = false
|
||||||
|
let listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false
|
||||||
|
|
||||||
|
function touchListener() {
|
||||||
|
userTouching = true
|
||||||
|
window.removeEventListener('touchstart', touchListener, listenerOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('touchstart', touchListener, listenerOptions)
|
||||||
|
|
||||||
export function isUserTouching() {
|
export function isUserTouching() {
|
||||||
return userTouching;
|
return userTouching
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isIOS() {
|
export function isIOS() {
|
||||||
return iOS;
|
return iOS
|
||||||
};
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user