This commit is contained in:
mgabdev 2020-02-20 19:57:29 -05:00
parent e37500c0cf
commit bebc39f150
61 changed files with 1181 additions and 802 deletions

View File

@ -8,6 +8,8 @@ import Avatar from '../avatar'
import DisplayName from '../display_name'
import IconButton from '../icon_button'
import Icon from '../icon'
import Button from '../button'
import Text from '../text'
const messages = defineMessages({
follow: { id: 'account.follow', defaultMessage: 'Follow' },
@ -126,9 +128,17 @@ class Account extends ImmutablePureComponent {
>
<DisplayName account={account} />
</NavLink>
<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)}
</button>
<Button
outline
narrow
color='brand'
backgroundColor='none'
className={_s.marginTop5PX}
>
<Text color='inherit'>
{intl.formatMessage(messages.follow)}
</Text>
</Button>
</div>
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>

View File

@ -203,7 +203,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
<div className={[_s.default, _s.marginLeft5PX].join(' ')}>
<Textarea
inputRef={this.setTextbox}
className={[_s.default, _s.backgroundWhite, _s.lineHeight125, _s.resizeNone, _s.paddingVertical15PX, _s.outlineFocusBrand, _s.fontSize16PX, _s.text, _s.displayBlock].join(' ')}
className={[_s.default, _s.backgroundColorPrimary, _s.lineHeight125, _s.resizeNone, _s.paddingVertical15PX, _s.outlineFocusBrand, _s.fontSize16PX, _s.text, _s.displayBlock].join(' ')}
disabled={disabled}
placeholder={placeholder}
autoFocus={autoFocus}

View File

@ -3,7 +3,7 @@ import Text from './text'
export default class Badge extends PureComponent {
static propTypes = {
children: PropTypes.string,
popover: PropTypes.string,
description: PropTypes.string,
}
state = {
@ -19,15 +19,17 @@ export default class Badge extends PureComponent {
}
render() {
const { children, popover } = this.props
const { children, description } = this.props
const { hovering } = this.state
return (
<div>
<Text color='secondary' size='small' className={_s.marginVertical5PX}>
{children}
</Text>
</div>
<Text
color='white'
size='extraSmall'
className={[_s.backgroundColorBrand, _s.paddingHorizontal5PX, _s.lineHeight125, _s.radiusSmall].join(' ')}
>
{children}
</Text>
)
}
}

View File

@ -1,5 +1,7 @@
import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import classNames from 'classnames/bind'
import Icon from './icon'
const cx = classNames.bind(_s)
@ -7,6 +9,7 @@ const COLORS = {
primary: 'primary',
secondary: 'secondary',
tertiary: 'tertiary',
white: 'white',
brand: 'brand',
error: 'error',
none: 'none',
@ -21,15 +24,22 @@ export default class Button extends PureComponent {
onClick: PropTypes.func,
className: PropTypes.string,
icon: PropTypes.string,
iconWidth: PropTypes.string,
iconHeight: PropTypes.string,
iconClassName: PropTypes.string,
color: PropTypes.string,
backgroundColor: PropTypes.string,
block: PropTypes.bool,
text: PropTypes.bool,
disabled: PropTypes.bool,
outline: PropTypes.bool,
narrow: PropTypes.bool,
underlineOnHover: PropTypes.bool,
}
static defaultProps = {
color: COLORS.brand,
color: COLORS.white,
backgroundColor: COLORS.brand,
}
handleClick = (e) => {
@ -47,7 +57,27 @@ export default class Button extends PureComponent {
}
render () {
const { block, className, disabled, text, to, children, href, outline, color } = this.props
const {
block,
className,
disabled,
text,
to,
icon,
iconWidth,
iconHeight,
iconClassName,
children,
href,
outline,
color,
backgroundColor,
underlineOnHover,
narrow,
...otherProps
} = this.props
const theIcon = !!icon ? <Icon id={icon} width={iconWidth} height={iconWidth} className={iconClassName} /> : undefined
// : todo :
const classes = cx(className, {
@ -57,26 +87,43 @@ export default class Button extends PureComponent {
cursorPointer: 1,
textAlignCenter: 1,
backgroundColorBrand: !text && !outline,
backgroundColorPrimary: backgroundColor === COLORS.white,
backgroundColorBrand: backgroundColor === COLORS.brand,
backgroundTransparent: backgroundColor === COLORS.none,
// colorPrimary: 1,
// colorSecondary: 1,
colorWhite: [].indexOf(color) > -1,
colorBrand: text || [].indexOf(color) > -1,
colorPrimary: color === COLORS.primary,
colorSecondary: color === COLORS.secondary,
colorWhite: color === COLORS.white,
colorBrand: color === COLORS.brand,
// borderColorBrand: 1,
// border1PX: 1,
borderColorBrand: color === COLORS.brand && outline,
border1PX: outline,
circle: !text,
paddingVertical10PX: !text,
paddingVertical5PX: narrow,
paddingVertical10PX: !text && !narrow,
paddingHorizontal15PX: !text,
width100PC: block,
underline_onHover: underlineOnHover,
backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand,
backgroundColorBrand_onHover: color === COLORS.brand && outline,
colorWhite_onHover: color === COLORS.brand && outline,
})
const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button'
const theChildren = !!icon ? (
<Fragment>
{theIcon}
{children}
</Fragment>
) : children
return React.createElement(
tagName,
{
@ -86,8 +133,9 @@ export default class Button extends PureComponent {
to: to || undefined,
to: href || undefined,
onClick: this.handleClick || undefined,
...otherProps
},
children,
theChildren,
)
}

View File

@ -1,5 +1,7 @@
import { injectIntl, defineMessages } from 'react-intl'
import Icon from './icon'
import Button from './button'
import Heading from './heading'
const messages = defineMessages({
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
@ -46,7 +48,7 @@ class ColumnHeader extends PureComponent {
this.historyBack()
}
render () {
render() {
const { title, showBackBtn, icon, active, children, actions, intl: { formatMessage } } = this.props
const { collapsed } = this.state
@ -58,11 +60,13 @@ class ColumnHeader extends PureComponent {
<Icon className={[_s.marginRight5PX, _s.fillColorBrand].join(' ')} id='back' width='20px' height='20px' />
</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(' ')}>
<div className={[_s.default, _s.height100PC, _s.justifyContentCenter].join(' ')}>
<Heading size='h1'>
{title}
</span>
</h1>
</Heading>
</div>
{
!!actions &&
<div className={[_s.default, _s.backgroundTransparent, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.marginLeftAuto].join(' ')}>
@ -81,75 +85,6 @@ class ColumnHeader extends PureComponent {
}
</div>
)
// const wrapperClassName = classNames('column-header__wrapper', {
// 'column-header__wrapper--active': active,
// })
// const buttonClassName = classNames('column-header', {
// 'column-header--active': active,
// })
// const btnTitle = formatMessage(collapsed ? messages.show : messages.hide)
// const hasTitle = icon && title
// const hasChildren = !!children
// if (!hasChildren && !hasTitle) {
// return null
// } else if (!hasChildren && hasTitle) {
// return (
// <div className={wrapperClassName}>
// <h1 className={buttonClassName}>
// <Icon id={icon} fixedWidth className='column-header__icon' />
// {title}
// </h1>
// </div>
// )
// }
// const collapsibleClassName = classNames('column-header__collapsible', {
// 'column-header__collapsible--collapsed': collapsed,
// })
// const collapsibleButtonClassName = classNames('column-header__button', {
// 'column-header__button--active': !collapsed,
// })
// return (
// <div className={wrapperClassName}>
// <h1 className={buttonClassName}>
// {
// hasTitle && (
// <Fragment>
// <Icon id={icon} fixedWidth className='column-header__icon' />
// {title}
// </Fragment>
// )
// }
// <button
// className={collapsibleButtonClassName}
// title={btnTitle}
// aria-label={btnTitle}
// aria-pressed={!collapsed}
// onClick={this.handleToggleClick}
// >
// <Icon id='sliders' />
// </button>
// </h1>
// <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null}>
// <div className='column-header__collapsible-inner'>
// {
// !collapsed &&
// <div key='extra-content' className='column-header__collapsible__extra'>
// {children}
// </div>
// }
// </div>
// </div>
// </div>
// )
}
}

View File

@ -1,17 +1,19 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Icon from './icon';
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Badge from './badge'
import Icon from './icon'
export default class DisplayName extends ImmutablePureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
multiline: PropTypes.bool,
};
}
render () {
const { account } = this.props;
const { account, multiline } = this.props
// : todo :
return (
<span className={[_s.default, _s.flexRow, _s.maxWidth100PC, _s.alignItemsCenter].join(' ')}>
<bdi className={[_s.text, _s.whiteSpaceNoWrap, _s.textOverflowEllipsis].join(' ')}>
@ -23,15 +25,24 @@ export default class DisplayName extends ImmutablePureComponent {
{
account.get('is_verified') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Verified Account' />
/*<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='PRO' />
<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='Donor' />
<Icon id='verified' width='15px' height='15px' className={[_s.default]} title='Investor' />*/
}
{ /*
account.get('is_pro') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab PRO' />
*/ }
{ /*
account.get('is_donor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Donor' />
*/ }
{ /*
account.get('is_investor') &&
<Icon id='verified' width='16px' height='16px' className={_s.default} title='Gab Investor' />
*/ }
<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')}
</span>
</span>
);
)
}
}

View File

@ -1,7 +1,7 @@
export default class Divider extends PureComponent {
render() {
return (
<div className={[_s.default, _s.borderBottom1PX, _s.bordercolorSecondary2, _s.marginBottom15PX, _s.width100PC].join(' ')} />
<div className={[_s.default, _s.borderBottom1PX, _s.borderColorSecondary2, _s.marginBottom15PX, _s.width100PC].join(' ')} />
)
}
}

View File

@ -0,0 +1,11 @@
import Text from './text'
export default class DotTextSeperator extends PureComponent {
render() {
return (
<Text size='small' color='secondary' className={_s.marginLeft5PX}></Text>
)
}
}

View File

@ -45,7 +45,7 @@ class GroupListItem extends ImmutablePureComponent {
if (!relationships) return null
const unreadCount = relationships.get('unread_count')
const subtitle = unreadCount > 0 ? (
<Fragment>
{shortNumberFormat(unreadCount)}
@ -57,7 +57,7 @@ class GroupListItem extends ImmutablePureComponent {
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(' ')}
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()}
>

View File

@ -4,6 +4,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom'
import { shortNumberFormat } from '../utils/numbers'
import Text from './text'
import Button from './button'
export default class HashtagItem extends ImmutablePureComponent {
@ -29,15 +30,29 @@ export default class HashtagItem extends ImmutablePureComponent {
return (
<NavLink
to='/test'
to='/tags/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}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<div>
<Text color='brand' size='medium' weight='bold' className={[_s.paddingVertical2PX, _s.lineHeight15].join(' ')}>
#randomhashtag
</Text>
</div>
<Button
text
backgroundColor='none'
color='none'
icon='caret-down'
iconWidth='8px'
iconHeight='8px'
iconClassName={_s.fillcolorSecondary}
className={_s.marginLeftAuto}
/>
</div>
<Text color='secondary' size='small' className={_s.paddingVertical2PX}>
10,240 Gabs
</Text>
</NavLink>

View File

@ -13,7 +13,6 @@ const SIZES = {
export default class Heading extends PureComponent {
static propTypes = {
className: PropTypes.string,
children: PropTypes.any,
size: PropTypes.oneOf(Object.keys(SIZES)),
}
@ -23,31 +22,36 @@ export default class Heading extends PureComponent {
}
render() {
const { className, children, size } = this.props
const { 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,
colorSecondary: [SIZES.h2, SIZES.h4, SIZES.h5].indexOf(size) > -1,
fontSize24PX: size === SIZES.h1,
fontSize19PX: size === SIZES.h2,
fontSize16PX: size === SIZES.h3,
fontSize13PX: size === SIZES.h4,
fontSize12PX: size === SIZES.h5,
marginTop5PX: [SIZES.h2, SIZES.h4].indexOf(size) > -1,
lineHeight2: size === SIZES.h5,
paddingVertical2PX: size === SIZES.h5,
// fontWeightNormal: weight === WEIGHTS.normal,
// fontWeightMedium: weight === WEIGHTS.medium,
fontWeightBold: [SIZES.h3, SIZES.h4].indexOf(size) > -1
fontWeightMedium: [SIZES.h1, SIZES.h5].indexOf(size) > -1,
fontWeightBold: [SIZES.h3, SIZES.h4].indexOf(size) > -1,
})
return React.createElement(
size,
{
className: classes,
role: 'heading',
},
children,
)

View File

@ -1,9 +1,65 @@
import classNames from 'classnames/bind'
import Button from './button'
import Icon from './icon'
const cx = classNames.bind(_s)
export default class Input extends PureComponent {
static propTypes = {
placeholder: PropTypes.string,
prependIcon: PropTypes.string,
value: PropTypes.string,
hasClear: PropTypes.bool,
onChange: PropTypes.func,
onKeyUp: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
onClear: PropTypes.func,
}
render() {
const { children } = this.props
const { placeholder, prependIcon, value, hasClear, onChange, onKeyUp, onFocus, onBlur, onClear } = this.props
const inputClasses = cx({
default: 1,
text: 1,
outlineNone: 1,
lineHeight125: 1,
displayBlock: 1,
paddingVertical10PX: 1,
backgroundTransparent: 1,
fontSize15PX: 1,
flexGrow1: 1,
paddingHorizontal5PX: !!prependIcon,
paddingLeft15PX: !prependIcon,
paddingRight15PX: !hasClear,
})
return (
<input />
<div className={[_s.default, _s.backgroundColorPrimary, _s.border1PX, _s.borderColorSecondary, _s.flexRow, _s.circle, _s.alignItemsCenter].join(' ')}>
{
!!prependIcon &&
<Icon id={prependIcon} width='16px' height='16px' className={[_s.marginLeft15PX, _s.marginRight5PX].join(' ')} />
}
<input
className={inputClasses}
type='text'
placeholder={placeholder}
value={value}
onChange={onChange}
onKeyUp={onKeyUp}
onFocus={onFocus}
onBlur={onBlur}
/>
{
hasClear &&
<div role='button' tabIndex='0' className={'btnClasses'} onClick={onClear}>
<Icon id='close' width='10px' height='10px' className={_s.fillColorWhite} aria-label='Clear' />
</div>
}
</div>
)
}
}

View File

@ -19,14 +19,14 @@ export default class DefaultLayout extends PureComponent {
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.bordercolorSecondary2, _s.borderLeft1PX].join(' ')}>
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.bordercolorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.borderBottom1PX, _s.borderColorSecondary2, _s.backgroundcolorSecondary3, _s.z3, _s.top0, _s.positionFixed].join(' ')}>
<div className={[_s.default, _s.height53PX, _s.paddingLeft15PX, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween].join(' ')}>
<div className={[_s.default, _s.width660PX].join(' ')}>
<div className={[_s.default, _s.width645PX].join(' ')}>
<ColumnHeader title={title} showBackBtn={showBackBtn} actions={actions} />
</div>
<div className={[_s.default, _s.width325PX].join(' ')}>
<div className={[_s.default, _s.width340PX].join(' ')}>
<Search />
</div>
</div>
@ -35,15 +35,15 @@ export default class DefaultLayout extends PureComponent {
<div className={[_s.default, _s.height53PX].join(' ')}></div>
<div className={[_s.default, _s.width1015PX, _s.flexRow, _s.justifyContentSpaceBetween, _s.paddingLeft15PX, _s.paddingVertical15PX].join(' ')}>
<div className={[_s.default, _s.width660PX, _s.z1].join(' ')}>
<div className={[_s.default, _s.width645PX, _s.z1].join(' ')}>
<div className={_s.default}>
{children}
</div>
</div>
<div className={[_s.default, _s.width325PX].join(' ')}>
<div className={[_s.default, _s.width340PX].join(' ')}>
<Sticky top={73} enabled>
<div className={[_s.default, _s.width325PX].join(' ')}>
<div className={[_s.default, _s.width340PX].join(' ')}>
{layout}
</div>
</Sticky>

View File

@ -16,7 +16,7 @@ export default class ProfileLayout extends PureComponent {
<Sidebar />
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.bordercolorSecondary2, _s.borderLeft1PX].join(' ')}>
<main role='main' className={[_s.default, _s.flexShrink1, _s.flexGrow1, _s.borderColorSecondary2, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.height350PX, _s.width100PC].join(' ')}>
<img

View File

@ -1,5 +1,4 @@
import moment from 'moment'
import classNames from 'classnames/bind'
import {
FormattedMessage,
defineMessages,
@ -11,8 +10,11 @@ import {
source_url,
me,
} from '../initial_state'
import Text from './text'
import Button from './button'
const messages = defineMessages({
help: { id: 'getting_started.help', defaultMessage: 'Help' },
invite: { id: 'getting_started.invite', defaultMessage: 'Invite people' },
hotkeys: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' },
security: { id: 'getting_started.security', defaultMessage: 'Security' },
@ -20,7 +22,7 @@ const messages = defineMessages({
developers: { id: 'getting_started.developers', defaultMessage: 'Developers' },
terms: { id: 'getting_started.terms', defaultMessage: 'Terms of Service' },
dmca: { id: 'getting_started.dmca', defaultMessage: 'DMCA' },
terms: { id: 'getting_started.terms_of_sale', defaultMessage: 'Terms of Sale' },
salesTerms: { id: 'getting_started.terms_of_sale', defaultMessage: 'Terms of Sale' },
privacy: { id: 'getting_started.privacy', defaultMessage: 'Privacy Policy' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
})
@ -31,9 +33,6 @@ const mapDispatchToProps = (dispatch) => ({
},
})
const cx = classNames.bind(_s)
const currentYear = moment().format('YYYY')
export default @connect(null, mapDispatchToProps)
@injectIntl
class LinkFooter extends PureComponent {
@ -43,29 +42,18 @@ class LinkFooter extends PureComponent {
onOpenHotkeys: PropTypes.func.isRequired,
}
state = {
hoveringItemIndex: null,
}
onMouseEnterLinkFooterItem = (i) => {
this.setState({
hoveringItemIndex: i,
})
}
onMouseLeaveLinkFooterItem = () => {
this.setState({
hoveringItemIndex: null,
})
}
render() {
const { onOpenHotkeys, intl } = this.props
const { hoveringItemIndex } = this.state
const currentYear = moment().format('YYYY')
const linkFooterItems = [
{
to: '#',
to: '/help',
text: intl.formatMessage(messages.help),
requiresUser: true,
},
{
onClick: onOpenHotkeys,
text: intl.formatMessage(messages.hotkeys),
requiresUser: true,
@ -93,7 +81,7 @@ class LinkFooter extends PureComponent {
},
{
to: '/about/sales',
text: intl.formatMessage(messages.terms),
text: intl.formatMessage(messages.salesTerms),
},
{
to: '/about/privacy',
@ -108,64 +96,50 @@ class LinkFooter extends PureComponent {
]
return (
<div className={[_s.default, _s.paddingHorizontal10PX].join(' ')}>
<div className={[_s.default, _s.paddingHorizontal10PX, _s.marginBottom15PX].join(' ')}>
<nav aria-label='Footer' role='navigation' className={[_s.default, _s.flexWrap, _s.flexRow].join(' ')}>
{
linkFooterItems.map((linkFooterItem, i) => {
if (linkFooterItem.requiresUser && !me) return null
const classes = cx({
default: 1,
fontSize13PX: 1,
text: 1,
marginVertical5PX: 1,
paddingRight15PX: 1,
cursorPointer: 1,
backgroundTransparent: 1,
colorSecondary: i !== hoveringItemIndex,
noUnderline: i !== hoveringItemIndex,
colorPrimary: i === hoveringItemIndex,
underline: i === hoveringItemIndex,
})
if (linkFooterItem.onClick) {
return (
<button
key={`link-footer-item-${i}`}
data-method={linkFooterItem.logout ? 'delete' : null}
onClick={linkFooterItem.onClick || null}
onMouseEnter={() => this.onMouseEnterLinkFooterItem(i)}
onMouseLeave={() => this.onMouseLeaveLinkFooterItem(i)}
className={classes}
>
{linkFooterItem.text}
</button>
)
}
return (
<a
<Button
text
underlineOnHover
color='none'
backgroundColor='none'
key={`link-footer-item-${i}`}
href={linkFooterItem.to}
data-method={linkFooterItem.logout ? 'delete' : null}
onMouseEnter={() => this.onMouseEnterLinkFooterItem(i)}
onMouseLeave={() => this.onMouseLeaveLinkFooterItem(i)}
className={classes}
onClick={linkFooterItem.onClick || null}
className={[_s.marginVertical5PX, _s.paddingRight15PX].join(' ')}
>
{linkFooterItem.text}
</a>
<Text size='small' color='secondary'>
{linkFooterItem.text}
</Text>
</Button>
)
})
}
<span className={[_s.default, _s.text, _s.fontSize13PX, _s.colorSecondary, _s.marginVertical5PX].join(' ')}>© {currentYear} Gab AI, Inc.</span>
</nav>
<p className={[_s.default, _s.text, _s.fontSize13PX, _s.colorSecondary, _s.marginTop10PX, _s.marginBottom15PX].join(' ')}>
<Text size='small' color='secondary' className={_s.marginTop10PX}>
© {currentYear} Gab AI, Inc.
</Text>
<Text size='small' color='secondary' tagName='p' className={_s.marginTop10PX}>
<FormattedMessage
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}.'
values={{ gitlab: <a href={source_url} className={[_s.inherit].join(' ')} rel='noopener' target='_blank'>{repository}</a> }}
values={{
gitlab: (
<a href={source_url} className={_s.inherit} rel='noopener' target='_blank'>
{repository}
</a>
)
}}
/>
</p>
</Text>
</div>
)
}

View File

@ -0,0 +1,38 @@
import ScrollableList from './scrollable_list'
import ListItem from './list_item'
export default class List extends PureComponent {
static propTypes = {
items: PropTypes.array,
scrollKey: PropTypes.string,
emptyMessage: PropTypes.any,
}
render() {
const { items, scrollKey, emptyMessage } = this.props
return (
<div className={[_s.default, _s.backgroundColorPrimary, _s.radiusSmall, _s.overflowHidden, _s.border1PX, _s.borderColorSecondary].join(' ')}>
<ScrollableList
scrollKey={scrollKey}
emptyMessage={emptyMessage}
>
{
items.map((item, i) => {
return (
<ListItem
key={`list-item-${i}`}
to={item.to}
title={item.title}
isLast={items.length - 1 === i}
/>
)
})
}
</ScrollableList>
</div>
)
}
}

View File

@ -0,0 +1,44 @@
import { NavLink } from 'react-router-dom'
import classNames from 'classnames/bind'
import Icon from './icon'
const cx = classNames.bind(_s)
export default class ListItem extends PureComponent {
static propTypes = {
isLast: PropTypes.bool,
to: PropTypes.string,
title: PropTypes.string,
}
render() {
const { to, title, 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={to} className={containerClasses} >
<span className={[_s.default, _s.text, _s.colorPrimary, _s.fontSize14PX].join(' ')}>
{title}
</span>
<Icon
id='angle-right'
width='10px'
height='10px'
className={[_s.marginLeftAuto, _s.fillColorBlack].join(' ')}
/>
</NavLink>
)
}
}

View File

@ -517,7 +517,7 @@ class MediaGallery extends PureComponent {
default: 1,
displayBlock: 1,
overflowHidden: 1,
bordercolorSecondary: size === 1,
borderColorSecondary: size === 1,
borderTop1PX: size === 1,
borderBottom1PX: size === 1,
paddingHorizontal5PX: size > 1,

View File

@ -31,24 +31,17 @@ class GroupSidebarPanel extends ImmutablePureComponent {
return (
<PanelLayout
title={intl.formatMessage(messages.title)}
buttonTitle={intl.formatMessage(messages.all)}
buttonTo='/groups/browse/member'
headerButtonTitle={intl.formatMessage(messages.all)}
headerButtonTo='/groups/browse/member'
footerButtonTitle={count > 6 ? intl.formatMessage(messages.show_all) : undefined}
footerButtonTo={count > 6 ? '/groups/browse/member' : undefined}
>
<div className={_s.default}>
{
groupIds.slice(0, 6).map(groupId => (
<GroupListItem
key={`group-panel-item-${groupId}`}
id={groupId}
/>
<GroupListItem key={`group-panel-item-${groupId}`} id={groupId} />
))
}
{
count > 6 &&
<Button to='/groups/browse/member' block text>
{intl.formatMessage(messages.show_all)}
</Button>
}
</div>
</PanelLayout>
)

View File

@ -44,7 +44,12 @@ class HashtagsPanel extends ImmutablePureComponent {
// }
return (
<PanelLayout title={intl.formatMessage(messages.title)} noPadding>
<PanelLayout
noPadding
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_all)}
footerButtonTo='/explore'
>
<div className={_s.default}>
{ /* hashtags && hashtags.map(hashtag => (
<HashtagingItem key={hashtag.get('name')} hashtag={hashtag} />
@ -55,9 +60,6 @@ class HashtagsPanel extends ImmutablePureComponent {
<HashtagItem />
<HashtagItem />
</div>
<Button to='/groups/browse/member' block text>
{intl.formatMessage(messages.show_all)}
</Button>
</PanelLayout>
)
}

View File

@ -40,8 +40,8 @@ class ListDetailsPanel extends ImmutablePureComponent {
return (
<PanelLayout
title={intl.formatMessage(messages.title, { count })}
buttonTitle={intl.formatMessage(messages.show_all)}
buttonAction={this.handleShowAllLists}
headerButtonTitle={intl.formatMessage(messages.show_all)}
headerButtonAction={this.handleShowAllLists}
>
<div className={_s.default}>

View File

@ -1,39 +1,57 @@
import Heading from '../heading'
import Button from '../button'
import Text from '../text'
export default class PanelLayout extends PureComponent {
static propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
children: PropTypes.node,
buttonTitle: PropTypes.string,
buttonAction: PropTypes.func,
buttonTo: PropTypes.func,
headerButtonTitle: PropTypes.string,
headerButtonAction: PropTypes.func,
headerButtonTo: PropTypes.func,
footerButtonTitle: PropTypes.string,
footerButtonAction: PropTypes.func,
footerButtonTo: PropTypes.func,
noPadding: PropTypes.bool,
}
render() {
const { title, subtitle, buttonTitle, buttonAction, buttonTo, noPadding, children } = this.props
const {
title,
subtitle,
headerButtonTitle,
headerButtonAction,
headerButtonTo,
footerButtonTitle,
footerButtonAction,
footerButtonTo,
noPadding,
children,
} = this.props
return (
<aside className={[_s.default, _s.backgroundWhite, _s.overflowHidden, _s.radiusSmall, _s.marginBottom15PX, _s.bordercolorSecondary, _s.border1PX].join(' ')}>
<aside className={[_s.default, _s.backgroundColorPrimary, _s.overflowHidden, _s.radiusSmall, _s.marginBottom15PX, _s.borderColorSecondary, _s.border1PX].join(' ')}>
{
(title || subtitle) &&
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.bordercolorSecondary, _s.borderBottom1PX].join(' ')}>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX, _s.borderColorSecondary, _s.borderBottom1PX].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
<Heading size='h3'>
{title}
</Heading>
{
(!!buttonTitle && (!!buttonAction || !!buttonTo)) &&
(!!headerButtonTitle && (!!headerButtonAction || !!headerButtonTo)) &&
<div className={[_s.default, _s.marginLeftAuto].join(' ')}>
<Button
text
to={buttonTo}
onClick={buttonAction}
className={[_s.default, _s.cursorPointer, _s.fontWeightBold, _s.text, _s.colorBrand, _s.fontSize13PX, _s.noUnderline].join(' ')}
backgroundColor='none'
color='brand'
to={headerButtonTo}
onClick={headerButtonAction}
>
{buttonTitle}
<Text size='small' color='inherit' weight='bold'>
{headerButtonTitle}
</Text>
</Button>
</div>
}
@ -57,6 +75,24 @@ export default class PanelLayout extends PureComponent {
{
noPadding && children
}
{
(!!footerButtonTitle && (!!footerButtonAction || !!footerButtonTo)) &&
<div className={[_s.default, _s.borderColorSecondary, _s.borderTop1PX].join(' ')}>
<Button
text
color='none'
backgroundColor='none'
to={footerButtonTo}
onClick={footerButtonAction}
className={[_s.paddingHorizontal15PX, _s.paddingVertical15PX, _s.backgroundSubtle_onHover].join(' ')}
>
<Text color='brand' align='left' size='medium'>
{footerButtonTitle}
</Text>
</Button>
</div>
}
</aside>
)
}

View File

@ -2,11 +2,12 @@ import { injectIntl, defineMessages } from 'react-intl'
// import { fetchTrends } from '../../actions/trends'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import TrendingItem from '../../components/trending_item'
import TrendingItem from '../trends_panel_item'
import PanelLayout from './panel_layout'
const messages = defineMessages({
title: { id:'trends.title', defaultMessage: 'Trending right now' },
show_all: { id: 'groups.sidebar-panel.show_all', defaultMessage: 'Show all' },
})
// const mapStateToProps = state => ({
@ -43,7 +44,12 @@ class TrendsPanel extends ImmutablePureComponent {
// }
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
<PanelLayout
noPadding
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_all)}
footerButtonTo='/explore'
>
<div className={_s.default}>
{ /* trends && trends.map(hashtag => (
<TrendingItem key={hashtag.get('name')} hashtag={hashtag} />
@ -52,7 +58,6 @@ class TrendsPanel extends ImmutablePureComponent {
<TrendingItem />
<TrendingItem />
<TrendingItem />
<TrendingItem />
</div>
</PanelLayout>
)

View File

@ -8,6 +8,7 @@ import PanelLayout from './panel_layout';
const messages = defineMessages({
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
title: { id: 'who_to_follow.title', defaultMessage: 'Who to Follow' },
show_more: { id: 'who_to_follow.more', defaultMessage: 'Show more' },
});
const mapStateToProps = state => ({
@ -47,7 +48,11 @@ class WhoToFollowPanel extends ImmutablePureComponent {
// }
return (
<PanelLayout title={intl.formatMessage(messages.title)}>
<PanelLayout
title={intl.formatMessage(messages.title)}
footerButtonTitle={intl.formatMessage(messages.show_more)}
footerButtonTo='/explore'
>
<div className={_s.default}>
{suggestions && suggestions.map(accountId => (
<AccountContainer

View File

@ -0,0 +1,105 @@
import classNames from 'classnames/bind'
import Overlay from 'react-overlays/lib/Overlay'
import {
changeSearch,
clearSearch,
submitSearch,
showSearch,
} from '../actions/search'
import SearchPopout from './search_popout'
import Input from './input'
const mapStateToProps = state => ({
value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']),
})
const mapDispatchToProps = dispatch => ({
onChange (value) {
dispatch(changeSearch(value))
},
onClear () {
dispatch(clearSearch())
},
onSubmit () {
dispatch(submitSearch())
},
onShow () {
dispatch(showSearch())
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
class Search extends PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
}
static propTypes = {
value: PropTypes.string.isRequired,
submitted: PropTypes.bool,
onShow: PropTypes.func.isRequired,
openInRoute: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onKeyUp: PropTypes.func.isRequired,
handleSubmit: PropTypes.func,
withOverlay: PropTypes.bool,
handleClear: PropTypes.func.isRequired,
}
state = {
expanded: false,
}
handleChange = (e) => {
this.props.onChange(e.target.value)
}
handleFocus = () => {
this.setState({ expanded: true })
this.props.onShow()
}
handleBlur = () => {
this.setState({ expanded: false })
}
render() {
const { value, submitted, onKeyUp, handleClear, handleSubmit, withOverlay } = this.props
const { expanded } = this.state
const hasValue = value ? value.length > 0 || submitted : 0
return (
<div className={[_s.default, _s.justifyContentCenter, _s.height53PX].join(' ')}>
<Input
hasClear
value={value}
prependIcon='search'
placeholder='Search on Gab...'
onChange={this.handleChange}
onKeyUp={onKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
onClear={handleClear}
/>
{
withOverlay &&
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
<SearchPopout />
</Overlay>
}
</div>
)
}
}

View File

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

View File

@ -1,93 +0,0 @@
import classNames from 'classnames/bind';
import Overlay from 'react-overlays/lib/Overlay';
import Icon from '../icon';
import SearchPopout from '../search_popout';
const cx = classNames.bind(_s)
export default class Search extends PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
// value: PropTypes.string.isRequired,
submitted: PropTypes.bool,
// onShow: PropTypes.func.isRequired,
openInRoute: PropTypes.bool,
// placeholder: PropTypes.string.isRequired,
searchTitle: PropTypes.string,
// onChange: PropTypes.func.isRequired,
// onKeyUp: PropTypes.func.isRequired,
handleSubmit: PropTypes.func,
withOverlay: PropTypes.bool,
// handleClear: PropTypes.func.isRequired,
};
state = {
expanded: false,
};
handleFocus = () => {
this.setState({ expanded: true });
this.props.onShow();
}
handleBlur = () => {
this.setState({ expanded: false });
}
render() {
const { value, submitted, placeholder, searchTitle, onKeyUp, handleClear, handleSubmit, withOverlay, onChange } = this.props;
const { expanded } = this.state;
const hasValue = value ? value.length > 0 || submitted : 0;
const btnClasses = cx({
default: 1,
cursorPointer: 1,
marginRight5PX: 1,
paddingHorizontal10PX: 1,
paddingVertical10PX: 1,
circle: 1,
backgroundColorBrandLight: 1,
displayNone: !hasValue,
})
return (
<div className={[_s.default, _s.justifyContentCenter, _s.height53PX].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={[_s.default, _s.marginLeft15PX, _s.marginRight10PX].join(' ')} />
<input
className={[_s.default, _s.text, _s.outlineFocusBrand, _s.lineHeight125, _s.displayBlock, _s.paddingVertical10PX, _s.paddingHorizontal10PX, _s.backgroundTransparent, _s.fontSize15PX, _s.flexGrow1].join(' ')}
type='text'
placeholder='Search on Gab...'
value={value}
onChange={onChange}
onKeyUp={onKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
/>
<div role='button' tabIndex='0' className={btnClasses} onClick={handleClear}>
<Icon id='close' width='10px' height='10px' className={_s.fillColorWhite} aria-label={placeholder} />
</div>
</div>
{
withOverlay &&
<Overlay show={expanded && !hasValue} placement='bottom' target={this}>
<SearchPopout />
</Overlay>
}
{
(searchTitle && handleSubmit) &&
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
}
</div>
);
}
}

View File

@ -1,69 +0,0 @@
.search {
position: relative;
&__input {
display: block;
padding: 7px 30px 6px 10px;
@include search-input();
}
&__icon {
&::-moz-focus-inner {
border: 0;
}
&::-moz-focus-inner,
&:focus {
outline: 0 !important;
}
.fa {
cursor: default;
display: inline-block;
position: absolute;
top: 8px;
right: 6px;
z-index: 2;
font-size: 16px;
color: $gab-placeholder-accent;
opacity: 0;
pointer-events: none;
@include size(18px);
&.active {
pointer-events: auto;
opacity: 1;
}
}
.fa-search.active {
pointer-events: none;
}
.fa-times-circle {
cursor: pointer;
font-size: 17px;
color: $gab-alert-red;
&:hover {
color: lighten($gab-alert-red, 7%);
}
}
}
@media screen and (max-width: 630px) and (max-height: 400px) {
will-change: margin-top;
transition: margin-top 400ms 100ms;
}
@media screen and (min-width: 360px) {
margin-bottom: 10px;
}
}
@media screen and (min-width: 895px) {
.search-page .search {
display: none;
}
}

View File

@ -275,7 +275,7 @@ class HeaderMenuItem extends PureComponent {
alignItemsCenter: 1,
radiusSmall: 1,
// border1PX: shouldShowActive,
// bordercolorSecondary: shouldShowActive,
// borderColorSecondary: shouldShowActive,
backgroundSubtle2: shouldShowActive,
})

View File

@ -47,7 +47,7 @@ export default class SidebarSectionItem extends PureComponent {
alignItemsCenter: 1,
radiusSmall: 1,
// border1PX: shouldShowActive,
// bordercolorSecondary: shouldShowActive,
// borderColorSecondary: shouldShowActive,
backgroundSubtle2: shouldShowActive,
})
@ -86,7 +86,7 @@ export default class SidebarSectionItem extends PureComponent {
to={to}
onMouseEnter={() => this.handleOnMouseEnter()}
onMouseLeave={() => this.handleOnMouseLeave()}
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.alignItemsStart, _s.flexGrow1].join(' ')}
className={[_s.default, _s.noUnderline, _s.cursorPointer, _s.width100PC, _s.alignItemsStart].join(' ')}
>
<div className={containerClasses}>
<div className={[_s.default]}>

View File

@ -16,6 +16,7 @@ import StatusContent from '../status_content';
import StatusActionBar from '../status_action_bar';
import Icon from '../icon';
import Poll from '../poll';
import StatusHeader from '../status_header'
// We use the component (and not the container) since we do not want
// to use the progress bar to show download progress
@ -403,10 +404,6 @@ class Status extends ImmutablePureComponent {
);
}
if (account === undefined || account === null) {
statusAvatar = <Avatar account={status.get('account')} size={50} />;
}
const handlers = this.props.muted
? {}
: {
@ -427,7 +424,7 @@ class Status extends ImmutablePureComponent {
return (
<HotKeys handlers={handlers}>
<div
className={[_s.default, _s.backgroundWhite, _s.radiusSmall, _s.marginBottom15PX, _s.border1PX, _s.bordercolorSecondary].join(' ')}
className={[_s.default, _s.backgroundColorPrimary, _s.radiusSmall, _s.marginBottom15PX, _s.border1PX, _s.borderColorSecondary].join(' ')}
tabIndex={this.props.muted ? null : 0}
data-featured={featured ? 'true' : null}
aria-label={textForScreenReader(intl, status, rebloggedByText)}
@ -445,60 +442,7 @@ class Status extends ImmutablePureComponent {
data-id={status.get('id')}
>
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.marginTop5PX].join(' ')}>
<div className={[_s.default, _s.marginRight10PX].join(' ')}>{statusAvatar}</div>
<div className={[_s.default, _s.alignItemsStart, _s.flexGrow1, _s.marginTop5PX].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.alignItemsStart].join(' ')}>
<NavLink
className={[_s.default, _s.flexRow, _s.alignItemsStart, _s.noUnderline].join(' ')}
to={`/${status.getIn(['account', 'acct'])}`}
title={status.getIn(['account', 'acct'])}
>
<DisplayName account={status.get('account')} />
</NavLink>
<Icon id='ellipsis' width='20px' height='20px' className={[_s.default, _s.fillcolorSecondary, _s.marginLeftAuto].join(' ')} />
</div>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.lineHeight15].join(' ')}>
<NavLink
to={statusUrl}
className={[_s.default, _s.text, _s.fontSize13PX, _s.noUnderline, _s.colorSecondary].join(' ')}
>
<RelativeTimestamp timestamp={status.get('created_at')} />
</NavLink>
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}></span>
<Icon id='globe' width='12px' height='12px' className={[_s.default, _s.displayInline, _s.marginLeft5PX, _s.fillcolorSecondary].join(' ')}/>
{
!!status.get('group') &&
<Fragment>
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}></span>
<NavLink
to={`/groups/${status.getIn(['group', 'id'])}`}
className={[_s.default, _s.text, _s.fontSize13PX, _s.marginLeft5PX, _s.colorPrimary].join(' ')}
>
{status.getIn(['group', 'title'])}
</NavLink>
</Fragment>
}
{
status.get('revised_at') !== null &&
<Fragment>
<span className={[_s.default, _s.text, _s.fontSize12PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}></span>
<button
onClick={() => other.onShowRevisions(status)}
className={[_s.default, _s.text, _s.fontSize13PX, _s.marginLeft5PX, _s.colorSecondary].join(' ')}
>
Edited
</button>
</Fragment>
}
</div>
</div>
</div>
</div>
<StatusHeader status={status} />
<div className={_s.default}>
<StatusContent

View File

@ -165,134 +165,6 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
handleEditClick = () => {
this.props.onEdit(this.props.status);
}
handlePinClick = () => {
this.props.onPin(this.props.status);
}
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
handleBlockClick = () => {
this.props.onBlock(this.props.status);
}
handleOpen = () => {
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`);
}
handleEmbed = () => {
this.props.onEmbed(this.props.status);
}
handleReport = () => {
this.props.onReport(this.props.status);
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
handleCopy = () => {
const url = this.props.status.get('url');
const textarea = document.createElement('textarea');
textarea.textContent = url;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
} catch (e) {
//
} finally {
document.body.removeChild(textarea);
}
}
handleGroupRemoveAccount = () => {
const { status } = this.props;
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']));
}
handleGroupRemovePost = () => {
const { status } = this.props;
this.props.onGroupRemoveStatus(status.getIn(['group', 'id']), status.get('id'));
}
_makeMenu = (publicStatus) => {
const { status, intl: { formatMessage }, withDismiss, withGroupAdmin } = this.props;
const mutingConversation = status.get('muted');
let menu = [];
menu.push({ text: formatMessage(messages.open), action: this.handleOpen });
if (publicStatus) {
menu.push({ text: formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: formatMessage(messages.embed), action: this.handleEmbed });
}
if (!me) {
return menu;
}
menu.push(null);
if (status.getIn(['account', 'id']) === me || withDismiss) {
menu.push({ text: formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
}
if (status.getIn(['account', 'id']) === me) {
if (publicStatus) {
menu.push({ text: formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
}
}
menu.push({ text: formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: formatMessage(messages.edit), action: this.handleEditClick });
} else {
menu.push({ text: formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push(null);
menu.push({ text: formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
menu.push({ text: formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
if (isStaff) {
menu.push(null);
menu.push({ text: formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
menu.push({ text: formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
}
if (withGroupAdmin) {
menu.push(null);
menu.push({ text: formatMessage(messages.group_remove_account), action: this.handleGroupRemoveAccount });
menu.push({ text: formatMessage(messages.group_remove_post), action: this.handleGroupRemovePost });
}
}
return menu;
}
render () {
const { status, intl: { formatMessage } } = this.props;
@ -311,8 +183,6 @@ class StatusActionBar extends ImmutablePureComponent {
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
);
const menu = this._makeMenu(publicStatus);
const items = [
{
title: formatMessage(messages.like),
@ -357,7 +227,7 @@ class StatusActionBar extends ImmutablePureComponent {
flexRow: 1,
width100PC: 1,
borderTop1PX: !shouldCondense,
bordercolorSecondary: !shouldCondense,
borderColorSecondary: !shouldCondense,
marginTop5PX: hasInteractions,
})
@ -404,17 +274,6 @@ class StatusActionBar extends ImmutablePureComponent {
<StatusActionBarItem key={`status-action-bar-item-${i}`} {...item} />
))
}
{/*<div className='status-action-bar__dropdown'>
<DropdownMenuContainer
status={status}
items={menu}
icon='ellipsis-h'
size={18}
direction='right'
title={formatMessage(messages.more)}
/>
</div>*/}
</div>
</div>
<div className='status-action-bar__comment'>

View File

@ -0,0 +1,268 @@
import { Fragment } from 'react'
import { NavLink } from 'react-router-dom'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import RelativeTimestamp from './relative_timestamp'
import DisplayName from './display_name'
import Text from './text'
import DotTextSeperator from './dot_text_seperator'
import Icon from './icon'
import Button from './button'
import Avatar from './avatar'
export default class StatusHeader extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map,
}
handleStatusOptionsClick() {
console.log("handleStatusOptionsClick")
}
handleOpenStatusEdits() {
console.log("handleOpenStatusEdits")
}
handleDeleteClick = () => {
this.props.onDelete(this.props.status, this.context.router.history);
}
handleEditClick = () => {
this.props.onEdit(this.props.status);
}
handlePinClick = () => {
this.props.onPin(this.props.status);
}
handleMentionClick = () => {
this.props.onMention(this.props.status.get('account'), this.context.router.history);
}
handleMuteClick = () => {
this.props.onMute(this.props.status.get('account'));
}
handleBlockClick = () => {
this.props.onBlock(this.props.status);
}
handleOpen = () => {
this.context.router.history.push(`/${this.props.status.getIn(['account', 'acct'])}/posts/${this.props.status.get('id')}`);
}
handleEmbed = () => {
this.props.onEmbed(this.props.status);
}
handleReport = () => {
this.props.onReport(this.props.status);
}
handleConversationMuteClick = () => {
this.props.onMuteConversation(this.props.status);
}
handleCopy = () => {
const url = this.props.status.get('url');
const textarea = document.createElement('textarea');
textarea.textContent = url;
textarea.style.position = 'fixed';
document.body.appendChild(textarea);
try {
textarea.select();
document.execCommand('copy');
} catch (e) {
//
} finally {
document.body.removeChild(textarea);
}
}
handleGroupRemoveAccount = () => {
const { status } = this.props;
this.props.onGroupRemoveAccount(status.getIn(['group', 'id']), status.getIn(['account', 'id']));
}
handleGroupRemovePost = () => {
const { status } = this.props;
this.props.onGroupRemoveStatus(status.getIn(['group', 'id']), status.get('id'));
}
_makeMenu = (publicStatus) => {
const { status, intl: { formatMessage }, withDismiss, withGroupAdmin } = this.props;
const mutingConversation = status.get('muted');
let menu = [];
menu.push({ text: formatMessage(messages.open), action: this.handleOpen });
if (publicStatus) {
menu.push({ text: formatMessage(messages.copy), action: this.handleCopy });
menu.push({ text: formatMessage(messages.embed), action: this.handleEmbed });
}
if (!me) return menu
menu.push(null);
if (status.getIn(['account', 'id']) === me || withDismiss) {
menu.push({ text: formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick });
menu.push(null);
}
if (status.getIn(['account', 'id']) === me) {
if (publicStatus) {
menu.push({ text: formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
}
}
menu.push({ text: formatMessage(messages.delete), action: this.handleDeleteClick });
menu.push({ text: formatMessage(messages.edit), action: this.handleEditClick });
} else {
menu.push({ text: formatMessage(messages.mention, { name: status.getIn(['account', 'username']) }), action: this.handleMentionClick });
menu.push(null);
menu.push({ text: formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick });
menu.push({ text: formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick });
menu.push({ text: formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport });
if (isStaff) {
menu.push(null);
menu.push({ text: formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
menu.push({ text: formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
}
if (withGroupAdmin) {
menu.push(null);
menu.push({ text: formatMessage(messages.group_remove_account), action: this.handleGroupRemoveAccount });
menu.push({ text: formatMessage(messages.group_remove_post), action: this.handleGroupRemovePost });
}
}
return menu;
}
render() {
const { status } = this.props
const statusUrl = `/${status.getIn(['account', 'acct'])}/posts/${status.get('id')}`;
// const menu = this._makeMenu(publicStatus);
// <div className='status-action-bar__dropdown'>
// <DropdownMenuContainer
// status={status}
// items={menu}
// icon='ellipsis-h'
// size={18}
// direction='right'
// title={formatMessage(messages.more)}
// />
// </div>
return (
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.marginTop5PX].join(' ')}>
<NavLink
to={`/${status.getIn(['account', 'acct'])}`}
title={status.getIn(['account', 'acct'])}
className={[_s.default, _s.marginRight10PX].join(' ')}
>
<Avatar account={status.get('account')} size={50} />
</NavLink>
<div className={[_s.default, _s.alignItemsStart, _s.flexGrow1, _s.marginTop5PX].join(' ')}>
<div className={[_s.default, _s.flexRow, _s.width100PC, _s.alignItemsStart].join(' ')}>
<NavLink
className={[_s.default, _s.flexRow, _s.alignItemsStart, _s.noUnderline].join(' ')}
to={`/${status.getIn(['account', 'acct'])}`}
title={status.getIn(['account', 'acct'])}
>
<DisplayName account={status.get('account')} />
</NavLink>
<Button
text
backgroundColor='none'
color='none'
icon='ellipsis'
iconWidth='20px'
iconHeight='20px'
iconClassName={_s.fillcolorSecondary}
className={_s.marginLeftAuto}
onClick={this.handleStatusOptionsClick}
/>
</div>
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.lineHeight15].join(' ')}>
<Button
text
underlineOnHover
backgroundColor='none'
color='none'
to={statusUrl}
>
<Text size='small' color='secondary'>
<RelativeTimestamp timestamp={status.get('created_at')} />
</Text>
</Button>
<DotTextSeperator />
<Icon id='globe' width='12px' height='12px' className={[_s.default, _s.displayInline, _s.marginLeft5PX, _s.fillcolorSecondary].join(' ')} />
{
!!status.get('group') &&
<Fragment>
<DotTextSeperator />
<Button
text
underlineOnHover
backgroundColor='none'
color='none'
to={`/groups/${status.getIn(['group', 'id'])}`}
className={_s.marginLeft5PX}
>
<Text size='small' color='secondary'>
{status.getIn(['group', 'title'])}
</Text>
</Button>
</Fragment>
}
{
status.get('revised_at') !== null &&
<Fragment>
<DotTextSeperator />
<Button
text
underlineOnHover
backgroundColor='none'
color='none'
onClick={this.handleOpenStatusEdits}
className={_s.marginLeft5PX}
>
<Text size='small' color='secondary'>
Edited
</Text>
</Button>
</Fragment>
}
</div>
</div>
</div>
</div>
)
}
}

View File

@ -8,6 +8,7 @@ const COLORS = {
brand: 'brand',
error: 'error',
white: 'white',
inherit: 'inherit',
}
const SIZES = {
@ -26,6 +27,11 @@ const WEIGHTS = {
extraBold: 'extraBold',
}
const ALIGNMENTS = {
center: 'center',
left: 'left',
}
export default class Text extends PureComponent {
static propTypes = {
tagName: PropTypes.string,
@ -34,6 +40,7 @@ export default class Text extends PureComponent {
color: PropTypes.oneOf(Object.keys(COLORS)),
size: PropTypes.oneOf(Object.keys(SIZES)),
weight: PropTypes.oneOf(Object.keys(WEIGHTS)),
align: PropTypes.oneOf(Object.keys(ALIGNMENTS)),
underline: PropTypes.bool,
}
@ -45,7 +52,16 @@ export default class Text extends PureComponent {
}
render() {
const { tagName, className, children, color, size, weight, underline } = this.props
const {
tagName,
className,
children,
color,
size,
weight,
underline,
align
} = this.props
const classes = cx(className, {
default: 1,
@ -55,15 +71,21 @@ export default class Text extends PureComponent {
colorSecondary: color === COLORS.secondary,
colorBrand: color === COLORS.brand,
colorWhite: color === COLORS.white,
inherit: color === COLORS.inherit,
fontSize19PX: size === SIZES.large,
fontSize15PX: size === SIZES.medium,
fontSize14PX: size === SIZES.normal,
fontSize13PX: size === SIZES.small,
fontSize12PX: size === SIZES.extraSmall,
fontWeightNormal: weight === WEIGHTS.normal,
fontWeightMedium: weight === WEIGHTS.medium,
fontWeightBold: weight === WEIGHTS.bold,
fontWeightExtraBold: weight === WEIGHTS.extraBold,
textAlignLeft: align === ALIGNMENTS.left,
textAlignCenter: align === ALIGNMENTS.center,
underline: underline,
})

View File

@ -1,19 +1,28 @@
import ImmutablePureComponent from 'react-immutable-pure-component';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { me } from '../initial_state';
import ComposeFormContainer from '../features/compose/containers/compose_form_container';
import Avatar from './avatar';
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { injectIntl, defineMessages } from 'react-intl'
import { me } from '../initial_state'
import ComposeFormContainer from '../features/compose/containers/compose_form_container'
import Avatar from './avatar'
import Heading from './heading'
const messages = defineMessages({
createPost: { id: 'column_header.create_post', defaultMessage: 'Create Post' },
})
const mapStateToProps = state => {
return {
account: state.getIn(['accounts', me]),
};
};
}
}
export default @connect(mapStateToProps)
export default
@connect(mapStateToProps)
@injectIntl
class TimelineComposeBlock extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
account: ImmutablePropTypes.map.isRequired,
size: PropTypes.number,
}
@ -23,14 +32,14 @@ class TimelineComposeBlock extends ImmutablePureComponent {
}
render() {
const { account, size, ...rest } = this.props;
const { account, size, intl, ...rest } = this.props
return (
<section className={[_s.default, _s.overflowHidden, _s.radiusSmall, _s.border1PX, _s.bordercolorSecondary, _s.backgroundWhite, _s.marginBottom15PX].join(' ')}>
<div className={[_s.default, _s.backgroundSubtle, _s.borderBottom1PX, _s.bordercolorSecondary, _s.paddingHorizontal15PX, _s.paddingVertical2PX].join(' ')}>
<h1 className={[_s.default, _s.text, _s.colorSecondary, _s.fontSize12PX, _s.fontWeightMedium, _s.lineHeight2, _s.paddingVertical2PX].join(' ')}>
Create Post
</h1>
<section className={[_s.default, _s.overflowHidden, _s.radiusSmall, _s.border1PX, _s.borderColorSecondary, _s.backgroundColorPrimary, _s.marginBottom15PX].join(' ')}>
<div className={[_s.default, _s.backgroundSubtle, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingHorizontal15PX, _s.paddingVertical2PX].join(' ')}>
<Heading size='h5'>
{intl.formatMessage(messages.createPost)}
</Heading>
</div>
<div className={[_s.default, _s.flexRow, _s.paddingVertical15PX, _s.paddingHorizontal15PX].join(' ')}>
<div className={[_s.default, _s.marginRight10PX].join(' ')}>

View File

@ -4,6 +4,11 @@ import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom'
import classNames from 'classnames/bind'
import { shortNumberFormat } from '../utils/numbers'
import Text from './text'
import Button from './button'
import Image from './image'
import TrendingItemCard from './trends_panel_item_card'
import DotTextSeperator from './dot_text_seperator'
const cx = classNames.bind(_s)
@ -42,12 +47,20 @@ export default class TrendingItem extends ImmutablePureComponent {
return (
<NavLink
to='/test'
className={[_s.default, _s.noUnderline, _s.marginBottom10PX].join(' ')}
className={[_s.default, _s.noUnderline, _s.paddingHorizontal15PX, _s.paddingVertical5PX, _s.borderColorSecondary, _s.borderBottom1PX, _s.backgroundSubtle_onHover].join(' ')}
onMouseEnter={() => this.handleOnMouseEnter()}
onMouseLeave={() => this.handleOnMouseLeave()}
>
<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>
<div className={[_s.default, _s.flexRow, _s.marginTop5PX].join(' ')}>
<Text size='small' color='secondary'>1</Text>
<DotTextSeperator />
<Text size='small' color='secondary' className={_s.marginLeft5PX}>Politics</Text>
</div>
<div className={[_s.default, _s.paddingVertical5PX].join(' ')}>
<Text color='primary' weight='bold' size='medium'>Trump Campaign</Text>
<Text color='secondary' className={[_s.marginTop5PX, _s.marginBottom10PX].join(' ')}>46.7K Gabs</Text>
<TrendingItemCard />
</div>
</NavLink>
)
}

View File

@ -0,0 +1,66 @@
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 classNames from 'classnames/bind'
import { shortNumberFormat } from '../utils/numbers'
import Text from './text'
import Button from './button'
import Image from './image'
const cx = classNames.bind(_s)
export default class TrendingItemCard extends ImmutablePureComponent {
static propTypes = {
trend: ImmutablePropTypes.map.isRequired,
};
state = {
hovering: false,
}
handleOnMouseEnter = () => {
this.setState({ hovering: true })
}
handleOnMouseLeave = () => {
this.setState({ hovering: false })
}
render() {
const { trend } = this.props
const { hovering } = this.state
const subtitleClasses = cx({
default: 1,
text: 1,
displayFlex: 1,
fontSize13PX: 1,
fontWeightNormal: 1,
colorSecondary: 1,
underline: hovering,
})
// URL with title, description
// URL with video
// URL with title, description, image
return (
<div className={[_s.default, _s.flexRow, _s.overflowHidden, _s.borderColorSecondary, _s.border1PX, _s.radiusSmall, _s.backgroundSubtle_onHover].join(' ')}>
<div className={[_s.default, _s.flexNormal, _s.paddingVertical10PX, _s.paddingHorizontal10PX].join(' ')}>
<Text color='secondary' className={_s.lineHeight15}>
NYPost
</Text>
<Text size='medium' color='primary'>
The best flower subscription services: BloomsyBox, Bouqs...
</Text>
</div>
<Image width='92px' height='92px' />
</div>
)
}
}

View File

@ -1,69 +0,0 @@
import { defineMessages, injectIntl } from 'react-intl';
import Search from '../../../../components/search';
const messages = defineMessages({
placeholder: { id: 'search.placeholder', defaultMessage: 'Search' },
});
export default @injectIntl
class ComposeSearch extends PureComponent {
static contextTypes = {
router: PropTypes.object.isRequired,
};
static propTypes = {
value: PropTypes.string.isRequired,
submitted: PropTypes.bool,
onChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
onShow: PropTypes.func.isRequired,
openInRoute: PropTypes.bool,
intl: PropTypes.object.isRequired,
};
handleChange = (e) => {
this.props.onChange(e.target.value);
}
handleClear = (e) => {
e.preventDefault();
if (this.props.value.length > 0 || this.props.submitted) {
this.props.onClear();
}
}
handleKeyUp = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
this.props.onSubmit();
if (this.props.openInRoute) {
this.context.router.history.push('/search');
}
} else if (e.key === 'Escape') {
document.querySelector('.ui').parentElement.focus();
}
}
render () {
const { intl, value, onShow, openInRoute } = this.props;
return (
<Search
value={value}
placeholder={intl.formatMessage(messages.placeholder)}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
handleClear={this.handleClear}
onShow={onShow}
withOverlay
openInRoute
/>
)
}
}

View File

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

View File

@ -1,7 +1,7 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import TrendingItem from '../../../../components/trending_item';
import TrendingItem from '../../../../components/trends_panel_item';
import Icon from '../../../../components/icon';
import { WhoToFollowPanel } from '../../../../components/panel';
// import TrendsPanel from '../../ui/components/trends_panel';

View File

@ -10,7 +10,7 @@ import {
import { mascot } from '../../initial_state';
import Motion from '../ui/util/optional_motion';
import ComposeFormContainer from './containers/compose_form_container';
import SearchContainer from './containers/search_container';
// import SearchContainer from './containers/search_container';
import SearchResultsContainer from './containers/search_results_container';
import NavigationBar from './components/navigation_bar';
import elephantUIPlane from '../../../images/logo_ui_column_footer.png';
@ -76,7 +76,7 @@ class Compose extends ImmutablePureComponent {
<div className='drawer' role='region' aria-label={intl.formatMessage(messages.compose)}>
{header}
{isSearchPage && <SearchContainer /> }
{ /* isSearchPage && <SearchContainer /> */ }
<div className='drawer__pager'>
{!isSearchPage && <div className='drawer__inner' onFocus={this.onFocus}>

View File

@ -1,34 +0,0 @@
import {
changeSearch,
clearSearch,
submitSearch,
showSearch,
} from '../../../actions/search';
import ComposeSearch from '../components/compose_search';
const mapStateToProps = state => ({
value: state.getIn(['search', 'value']),
submitted: state.getIn(['search', 'submitted']),
});
const mapDispatchToProps = dispatch => ({
onChange (value) {
dispatch(changeSearch(value));
},
onClear () {
dispatch(clearSearch());
},
onSubmit () {
dispatch(submitSearch());
},
onShow () {
dispatch(showSearch());
},
});
export default connect(mapStateToProps, mapDispatchToProps)(ComposeSearch);

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import { setupListAdder, resetListAdder } from '../../actions/lists';
import List from './components/list';
import Account from '../../components/account';
import IconButton from '../../components/icon_button';
import NewListForm from '../lists/components/new_list_form';
import NewListForm from '../lists_directory/components/new_list_form';
const getOrderedLists = createSelector([state => state.get('lists')], lists => {
if (!lists) {

View File

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

View File

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

View File

@ -1,13 +1,10 @@
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { NavLink } from 'react-router-dom'
import { createSelector } from 'reselect'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { fetchLists } from '../../actions/lists'
import ColumnIndicator from '../../components/column_indicator'
import ScrollableList from '../../components/scrollable_list'
import Icon from '../../components/icon'
import List from '../../components/list'
const messages = defineMessages({
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
@ -30,11 +27,9 @@ const mapDispatchToProps = (dispatch) => ({
},
})
const cx = classNames.bind(_s)
export default @connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class Lists extends ImmutablePureComponent {
class ListsDirectory extends ImmutablePureComponent {
static propTypes = {
params: PropTypes.object.isRequired,
@ -63,56 +58,18 @@ class Lists extends ImmutablePureComponent {
const emptyMessage = intl.formatMessage(messages.empty)
const listItems = lists.map(list => ({
to: `/list/${list.get('id')}`,
title: list.get('title'),
}))
return (
<ScrollableList
<List
scrollKey='lists'
emptyMessage={emptyMessage}
>
<div className={[_s.default, _s.backgroundWhite, _s.radiusSmall, _s.overflowHidden, _s.border1PX, _s.bordercolorSecondary].join(' ')}>
{
lists.map((list, i) => {
const isLast = lists.length - 1 === i
return (
<ListItem key={list.get('id')} list={list} isLast={isLast} />
)
})
}
</div>
</ScrollableList>
items={listItems}
/>
)
}
}
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

@ -1,4 +1,4 @@
import SearchContainer from '../compose/containers/search_container';
// import SearchContainer from '../compose/containers/search_container';
import SearchResultsContainer from '../compose/containers/search_results_container';
export default class Search extends PureComponent {
@ -6,7 +6,7 @@ export default class Search extends PureComponent {
render() {
return (
<div className='column search-page'>
<SearchContainer />
{ /* <SearchContainer /> */ }
<div className='drawer__pager'>
<div className='drawer__inner darker'>

View File

@ -161,7 +161,7 @@ export default class Card extends ImmutablePureComponent {
)
const description = (
<div className={[_s.default, _s.flexNormal, _s.paddingHorizontal10PX, _s.paddingVertical10PX, _s.bordercolorSecondary, _s.borderLeft1PX].join(' ')}>
<div className={[_s.default, _s.flexNormal, _s.paddingHorizontal10PX, _s.paddingVertical10PX, _s.borderColorSecondary, _s.borderLeft1PX].join(' ')}>
{title}
<p className={[_s.default, _s.displayFlex, _s.text, _s.marginVertical5PX, _s.overflowWrapBreakWord, _s.colorSecondary, _s.fontSize13PX, _s.fontWeightNormal].join(' ')}>
{trim(card.get('description') || '', maxDescription)}
@ -192,7 +192,7 @@ export default class Card extends ImmutablePureComponent {
return (
<div className={[_s.default, _s.width100PC, _s.paddingHorizontal10PX].join(' ')}>
<div className={[_s.default, _s.overflowHidden, _s.width100PC, _s.bordercolorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}>
<div className={[_s.default, _s.overflowHidden, _s.width100PC, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}>
<div className={[_s.default, _s.width100PC].join(' ')}>
<div className={[_s.default, _s.width100PC, _s.paddingTop5625PC].join(' ')}>
{ !!embed && embed}
@ -231,7 +231,7 @@ export default class Card extends ImmutablePureComponent {
<div className={[_s.default, _s.width100PC, _s.paddingHorizontal10PX].join(' ')}>
<a
href={card.get('url')}
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.bordercolorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
className={[_s.default, _s.cursorPointer, _s.flexRow, _s.overflowHidden, _s.noUnderline, _s.width100PC, _s.borderColorSecondary2, _s.border1PX, _s.radiusSmall].join(' ')}
rel='noopener'
ref={this.setRef}
>

View File

@ -60,7 +60,7 @@ import {
// Groups,
// GroupTimeline,
ListTimeline,
Lists,
ListsDirectory,
// GroupMembers,
// GroupRemovedAccounts,
// GroupCreate,
@ -195,7 +195,7 @@ class SwitchingColumnsArea extends PureComponent {
<WrappedRoute path='/tags/:id' publicRoute component={HashtagTimeline} content={children} />
*/}
<WrappedRoute path='/lists' page={ListsPage} component={Lists} content={children} />
<WrappedRoute path='/lists' page={ListsPage} component={ListsDirectory} content={children} />
<WrappedRoute path='/list/:id' page={ListPage} component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' page={NotificationsPage} component={Notifications} content={children} />

View File

@ -50,8 +50,8 @@ export function Groups () {
return import(/* webpackChunkName: "features/groups/index" */'../../groups/index');
}
export function Lists () {
return import(/* webpackChunkName: "features/lists" */'../../lists');
export function ListsDirectory () {
return import(/* webpackChunkName: "features/lists_directory" */'../../lists_directory');
}
export function Status () {

View File

@ -2228,7 +2228,7 @@
"id": "getting_started.security"
},
{
"defaultMessage": "About this server",
"defaultMessage": "About",
"id": "navigation_bar.info"
},
{

View File

@ -240,7 +240,7 @@
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.follows_and_followers": "Follows and followers",
"navigation_bar.info": "About this server",
"navigation_bar.info": "About",
"navigation_bar.keyboard_shortcuts": "Hotkeys",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",

View File

@ -237,7 +237,7 @@
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",
"navigation_bar.follow_requests": "Follow requests",
"navigation_bar.info": "About this server",
"navigation_bar.info": "About",
"navigation_bar.keyboard_shortcuts": "Hotkeys",
"navigation_bar.lists": "Lists",
"navigation_bar.logout": "Logout",

View File

@ -22,11 +22,11 @@ export default class ListsPage extends PureComponent {
title='Lists'
actions={[
{
icon: 'subtract',
icon: 'list-delete',
onClick: this.handleClickEditLists
},
{
icon: 'add',
icon: 'list-add',
onClick: this.handleClickNewList
},
]}

View File

@ -95,14 +95,15 @@ body {
.whiteSpaceNoWrap { white-space: nowrap; }
.outlineFocusBrand:focus {outline: 2px solid #21cf7a; }
.outlineNone { outline: none; }
.outlineFocusBrand:focus { outline: 2px solid #21cf7a; }
.resizeNone { resize: none; }
.circle { border-radius: 9999px; }
.radiusSmall { border-radius: 8px; }
.bordercolorSecondary2 { border-color: #e5e9ed; }
.bordercolorSecondary { border-color: #ECECED; }
.borderColorSecondary2 { border-color: #e5e9ed; }
.borderColorSecondary { border-color: #ECECED; }
.borderColorWhite { border-color: #fff; }
.borderColorBrand { border-color: #21cf7a; }
.borderRight1PX { border-right-width: 1px; }
@ -131,15 +132,17 @@ body {
.backgroundSubtle_onHover:hover { background-color: #F5F8FA; }
.backgroundSubtle2 { background-color: #e8ecef; }
.backgroundcolorSecondary3 { background-color: #F6F6F9; }
.backgroundWhite { background-color: #fff; }
.backgroundColorPrimary { background-color: #fff; }
.backgroundColorBrandLightOpaque { background-color: rgba(54, 233, 145, 0.1); }
.backgroundColorOpaque { background-color: rgba(0,0,0, 0.4); }
.backgroundColorBrandLight { background-color: #36e991; }
.backgroundColorBrand { background-color: #21cf7a; }
.backgroundColorBrand_onHover:hover { background-color: #21cf7a; }
.backgroundColorBrandDark { background-color: #38A16B; }
.backgroundColorBrandDark_onHover:hover { background-color: #38A16B; }
.colorPrimary { color: #000; }
.colorWhite { color: #fff; }
.colorWhite_onHover:hover { color: #fff; }
.colorSecondary { color: #4B4F55; }
.colorBrand { color: #21cf7a }
.fillColorBlack { fill: #000; }
@ -175,9 +178,9 @@ body {
.height350PX { height: 350px; }
.width1015PX { width: 1015px; }
.width660PX { width: 660px; }
.width645PX { width: 645px; }
.width400PX { width: 400px; }
.width325PX { width: 325px; }
.width340PX { width: 340px; }
.width240PX { width: 240px; }
.width100PC { width: 100%; }
.width72PX { width: 72px; }
@ -185,42 +188,43 @@ body {
@media (min-width: 1480px) {
.width1015PX { width: 1080px; }
.width660PX { width: 700px; }
.width325PX { width: 350px; }
.width645PX { width: 700px; }
.width340PX { width: 350px; }
.width240PX { width: 250px; }
}
@media (min-width: 1160px) and (max-width: 1280px) {
.width1015PX { width: 910px; }
.width660PX { width: 580px; }
.width325PX { width: 300px; }
.width645PX { width: 580px; }
.width340PX { width: 300px; }
.width240PX { width: 230px; }
}
@media (min-width: 1080px) and (max-width: 1160px) {
.width1015PX { width: 850px; }
.width660PX { width: 525px; }
.width325PX { width: 300px; }
.width645PX { width: 525px; }
.width340PX { width: 300px; }
.width240PX { width: 210px; }
}
@media (min-width: 992px) and (max-width: 1080px) {
.width1015PX { width: 850px; }
.width660PX { width: 525px; }
.width325PX { width: 300px; }
.width645PX { width: 525px; }
.width340PX { width: 300px; }
.width240PX { width: 100px; }
}
@media (min-width: 0px) and (max-width: 992px) {
.width1015PX { width: 600px; }
.width660PX { width: 600px; }
.width325PX { width: 0px; }
.width645PX { width: 600px; }
.width340PX { width: 0px; }
.width240PX { width: 100px; }
}
.top0 { top: 0; }
.top60PC { top: 60%; }
.textAlignLeft { text-align: left; }
.textAlignCenter { text-align: center; }
.fontSize24PX { font-size: 24px; }