This commit is contained in:
mgabdev 2020-02-19 18:57:07 -05:00
parent be3daea78b
commit e37500c0cf
105 changed files with 1975 additions and 1393 deletions

View 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,
};
};

View File

@ -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 = () => ({

View File

@ -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,
};
};

View File

@ -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>
`;

View File

@ -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();
});
});

View File

@ -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>

View File

@ -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'/>

View File

@ -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}

View 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} />
)
}
}

View File

@ -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')}
/>
)
}
}

View File

@ -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;
}

View File

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

View File

@ -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>
);
}
}

View File

@ -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;
}
}

View File

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

View 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>
)
}
}

View 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,
)
}
}

View File

@ -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>
);
}
}

View File

@ -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;
}
}
}

View File

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

View File

@ -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>
); );

View File

@ -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>
);
}
}

View File

@ -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;
}
}

View File

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

View File

@ -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>
) )

View File

@ -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>
);
}
}

View File

@ -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;
}
}
}

View File

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

View File

@ -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>
} }

View File

@ -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>
);
}
};

View File

@ -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;
}
}

View File

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

View File

@ -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>
);
}
};

View File

@ -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);
}

View File

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

View File

@ -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>

View 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(' ')} />
)
}
}

View 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)}
&nbsp;
{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>
)
}
}

View 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>
)
}
}

View 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,
)
}
}

View File

@ -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':

View 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

View File

@ -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

View File

@ -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>
) )

View File

@ -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>

View File

@ -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

View 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}
/>
)
}
}

View File

@ -0,0 +1,9 @@
export default class Input extends PureComponent {
render() {
const { children } = this.props
return (
<input />
)
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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}

View File

@ -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,

View File

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

View File

@ -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%);
}

View File

@ -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)}
&nbsp;
{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>
)
}
}

View 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>
)
}
}

View File

@ -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>
);
};
};

View File

@ -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>
) )
} }

View File

@ -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>
) )
} }

View File

@ -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>
); )
} }
}; }

View 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>
)
}
}

View File

@ -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}

View File

@ -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 => {

View File

@ -0,0 +1,9 @@
export default class Popover extends PureComponent {
render() {
return (
<div>
{ /* */ }
</div>
)
}
}

View File

@ -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>
) )
} }

View File

@ -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,

View File

@ -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>

View 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>
)
}
}

View File

@ -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 &&

View 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>
)
}
}

View 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>
)
}
}

View File

@ -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}

View File

@ -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} />

View File

@ -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')}

View File

@ -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,

View 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,
)
}
}

View File

@ -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} />

View File

@ -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>
) )

View File

@ -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>
)
}
}

View 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>
)
}
}

View File

@ -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>

View File

@ -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,

View File

@ -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>

View File

@ -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>
} }

View File

@ -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>

View File

@ -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>
);
} }
} }

View File

@ -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>

View File

@ -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} />)}

View File

@ -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>
); );
} }

View File

@ -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>
)
}
}

View File

@ -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}
> >

View File

@ -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} />

View File

@ -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>
) )

View 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>
)
}
}

View 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>
)
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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