This commit is contained in:
mgabdev 2020-03-25 23:11:32 -04:00
parent 0f01c1bc97
commit 3d0a85cde4
68 changed files with 1206 additions and 1275 deletions

View File

@ -0,0 +1,54 @@
import classNames from 'classnames/bind'
const cx = classNames.bind(_s)
// : todo :
export default class AccountActionButton extends PureComponent {
static propTypes = {
children: PropTypes.any,
size: PropTypes.oneOf(Object.keys(SIZES)),
center: PropTypes.bool,
}
static defaultProps = {
size: SIZES.h1,
}
render() {
const { children, size, center } = this.props
const classes = cx({
default: 1,
text: 1,
textAlignCenter: center,
colorPrimary: [SIZES.h1, SIZES.h3].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,
mt5: [SIZES.h2, SIZES.h4].indexOf(size) > -1,
lineHeight2: size === SIZES.h5,
py2: size === SIZES.h5,
// fontWeightNormal: weight === WEIGHTS.normal,
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,5 +1,5 @@
import { defineMessages, injectIntl } from 'react-intl' import { defineMessages, injectIntl } from 'react-intl'
import IconButton from './icon_button' import Button from './button'
const messages = defineMessages({ const messages = defineMessages({
title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' }, title: { id: 'bundle_column_error.title', defaultMessage: 'Network error' },
@ -25,7 +25,7 @@ class BundleColumnError extends PureComponent {
return ( return (
<div className='error-column'> <div className='error-column'>
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} /> <Button title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
{formatMessage(messages.body)} {formatMessage(messages.body)}
</div> </div>
) )

View File

@ -1,5 +1,5 @@
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import IconButton from './icon_button'; import Button from './button';
const messages = defineMessages({ const messages = defineMessages({
error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' }, error: { id: 'bundle_modal_error.message', defaultMessage: 'Something went wrong while loading this component.' },
@ -29,7 +29,7 @@ class BundleModalError extends PureComponent {
return ( return (
<div className='modal-root__modal error-modal'> <div className='modal-root__modal error-modal'>
<div className='error-modal__body'> <div className='error-modal__body'>
<IconButton title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} /> <Button title={formatMessage(messages.retry)} icon='refresh' onClick={this.handleRetry} size={64} />
{formatMessage(messages.error)} {formatMessage(messages.error)}
</div> </div>

View File

@ -10,8 +10,9 @@ const COLORS = {
secondary: 'secondary', secondary: 'secondary',
tertiary: 'tertiary', tertiary: 'tertiary',
white: 'white', white: 'white',
black: 'black',
brand: 'brand', brand: 'brand',
error: 'error', danger: 'danger',
none: 'none', none: 'none',
} }
@ -87,12 +88,11 @@ export default class Button extends PureComponent {
<Icon <Icon
id={icon} id={icon}
width={iconWidth} width={iconWidth}
height={iconWidth} height={iconHeight}
className={iconClassName} className={iconClassName}
/> />
) : undefined ) : undefined
// : todo :
const classes = noClasses ? className : cx(className, { const classes = noClasses ? className : cx(className, {
default: 1, default: 1,
noUnderline: 1, noUnderline: 1,
@ -101,18 +101,22 @@ export default class Button extends PureComponent {
textAlignCenter: 1, textAlignCenter: 1,
outlineNone: 1, outlineNone: 1,
flexRow: !!children && !!icon, flexRow: !!children && !!icon,
cursorNotAllowed: disabled,
opacity05: disabled,
backgroundColorPrimary: backgroundColor === COLORS.white, backgroundColorPrimary: backgroundColor === COLORS.white,
backgroundColorBlack: backgroundColor === COLORS.black,
backgroundColorBrand: backgroundColor === COLORS.brand, backgroundColorBrand: backgroundColor === COLORS.brand,
backgroundTransparent: backgroundColor === COLORS.none, backgroundTransparent: backgroundColor === COLORS.none,
backgroundSubtle2: backgroundColor === COLORS.tertiary, backgroundSubtle2: backgroundColor === COLORS.tertiary,
backgroundSubtle: backgroundColor === COLORS.secondary, backgroundSubtle: backgroundColor === COLORS.secondary,
backgroundColorDanger: backgroundColor === COLORS.danger,
colorPrimary: color === COLORS.primary, colorPrimary: !!children && color === COLORS.primary,
colorSecondary: color === COLORS.secondary, colorSecondary: !!children && color === COLORS.secondary,
colorTertiary: color === COLORS.tertiary, colorTertiary: !!children && color === COLORS.tertiary,
colorWhite: color === COLORS.white, colorWhite: !!children && color === COLORS.white,
colorBrand: color === COLORS.brand, colorBrand: !!children && color === COLORS.brand,
borderColorBrand: color === COLORS.brand && outline, borderColorBrand: color === COLORS.brand && outline,
border1PX: outline, border1PX: outline,
@ -128,12 +132,16 @@ export default class Button extends PureComponent {
underline_onHover: underlineOnHover, underline_onHover: underlineOnHover,
backgroundSubtle2Dark_onHover: backgroundColor === COLORS.tertiary, backgroundSubtle2Dark_onHover: backgroundColor === COLORS.tertiary || backgroundColor === COLORS.secondary,
backgroundColorBlackOpaque_onHover: backgroundColor === COLORS.black,
backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand, backgroundColorBrandDark_onHover: backgroundColor === COLORS.brand,
backgroundColorBrand_onHover: color === COLORS.brand && outline, backgroundColorBrand_onHover: color === COLORS.brand && outline,
colorWhite_onHover: color === COLORS.brand && outline, colorWhite_onHover: !!children && color === COLORS.brand && outline,
fillColorWhite: !!icon && color === COLORS.white,
fillColorBrand: !!icon && color === COLORS.brand,
fillColorWhite_onHover: !!icon && color === COLORS.brand && outline,
}) })
const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button' const tagName = !!href ? 'a' : !!to ? 'NavLink' : 'button'

View File

@ -1,5 +1,5 @@
import { defineMessages, injectIntl } from 'react-intl'; import { defineMessages, injectIntl } from 'react-intl';
import IconButton from './icon_button'; import Button from './button';
const messages = defineMessages({ const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
@ -30,7 +30,7 @@ class Domain extends PureComponent {
</span> </span>
<div className='domain__buttons'> <div className='domain__buttons'>
<IconButton <Button
active active
icon='unlock' icon='unlock'
title={intl.formatMessage(messages.unblockDomain, { title={intl.formatMessage(messages.unblockDomain, {

View File

@ -14,9 +14,19 @@ export default class FloatingActionButton extends Component {
render() { render() {
const { onClick, message } = this.props; const { onClick, message } = this.props;
// const shouldHideFAB = path => path.match(/^\/posts\/|^\/search|^\/getting-started/);
// const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <button key='floating-action-button' onClick={this.handleOpenComposeModal} className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}></button>;
return ( return (
<Button onClick={onClick} className='floating-action-button' aria-label={message}> <Button
onClick={onClick}
color='white'
className={[_s.positionFixed, _s.z4, _s.bottom0, _s.right0].join(' ')}
title={message}
aria-label={message}
>
<Icon id='compose' /> <Icon id='compose' />
[...]
</Button> </Button>
) )
} }

View File

@ -1,96 +0,0 @@
import classNames from 'classnames';
import spring from 'react-motion/lib/spring';
import Motion from '../../features/ui/util/optional_motion';
import Icon from '../icon';
export default class IconButton extends PureComponent {
static propTypes = {
className: PropTypes.string,
title: PropTypes.string.isRequired,
icon: PropTypes.string.isRequired,
onClick: PropTypes.func,
size: PropTypes.number,
active: PropTypes.bool,
pressed: PropTypes.bool,
expanded: PropTypes.bool,
style: PropTypes.object,
disabled: PropTypes.bool,
inverted: PropTypes.bool,
animate: PropTypes.bool,
overlay: PropTypes.bool,
tabIndex: PropTypes.string,
text: PropTypes.string,
width: PropTypes.string,
height: PropTypes.string,
};
static defaultProps = {
size: 18,
active: false,
disabled: false,
animate: false,
overlay: false,
tabIndex: '0',
};
handleClick = (e) => {
e.preventDefault();
if (!this.props.disabled) {
this.props.onClick(e);
}
}
render () {
const style = {
// fontSize: `${this.props.size}px`,
// width: `${this.props.size * 1.28571429}px`,
// height: `${this.props.size * 1.28571429}px`,
// lineHeight: `${this.props.size}px`,
...this.props.style,
};
const {
active,
animate,
className,
disabled,
expanded,
icon,
inverted,
overlay,
pressed,
tabIndex,
title,
text,
width,
height,
} = this.props;
const classes = classNames(className, 'icon-button', {
active,
disabled,
inverted,
overlayed: overlay,
});
return (
<button
aria-label={title}
aria-pressed={pressed}
aria-expanded={expanded}
title={title}
className={classes}
onClick={this.handleClick}
style={style}
tabIndex={tabIndex}
disabled={disabled}
>
<Icon id={icon} fixedWidth aria-hidden='true' width={width} height={height} />
{!!text && text}
</button>
);
}
}

View File

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

View File

@ -20,6 +20,8 @@ export default class Input extends PureComponent {
small: PropTypes.bool, small: PropTypes.bool,
readOnly: PropTypes.string, readOnly: PropTypes.string,
inputRef: PropTypes.func, inputRef: PropTypes.func,
id: PropTypes.string,
hideLabel: PropTypes.bool,
} }
render() { render() {
@ -36,20 +38,25 @@ export default class Input extends PureComponent {
title, title,
small, small,
readOnly, readOnly,
inputRef inputRef,
id,
hideLabel
} = this.props } = this.props
const inputClasses = cx({ const inputClasses = cx({
default: 1, default: 1,
text: 1, text: 1,
outlineNone: 1, outlineNone: 1,
lineHeight125: 1, lineHeight125: !small,
lineHeight1: small,
displayBlock: 1, displayBlock: 1,
py10: 1, py10: !small,
py5: small,
backgroundTransparent: !readOnly, backgroundTransparent: !readOnly,
backgroundSubtle2: readOnly, backgroundSubtle2: readOnly,
colorSecondary: readOnly, colorSecondary: readOnly,
fontSize15PX: 1, fontSize15PX: !small,
fontSize13PX: small,
flexGrow1: 1, flexGrow1: 1,
circle: 1, circle: 1,
px5: !!prependIcon, px5: !!prependIcon,
@ -57,12 +64,19 @@ export default class Input extends PureComponent {
pr15: !hasClear, pr15: !hasClear,
}) })
const titleClasses = cx({
default: 1,
mb10: 1,
pl15: 1,
displayNone: hideLabel,
})
return ( return (
<Fragment> <Fragment>
{ {
!!title && !!title &&
<div className={[_s.default, _s.mb10, _s.pl15].join(' ')}> <div className={titleClasses}>
<Text size='small' weight='medium' color='secondary'> <Text htmlFor={id} size='small' weight='medium' color='secondary' tagName='label'>
{title} {title}
</Text> </Text>
</div> </div>
@ -74,6 +88,7 @@ export default class Input extends PureComponent {
} }
<input <input
id={id}
className={inputClasses} className={inputClasses}
type='text' type='text'
placeholder={placeholder} placeholder={placeholder}

View File

@ -7,7 +7,7 @@ import { decode } from 'blurhash';
import { autoPlayGif, displayMedia } from '../../initial_state'; import { autoPlayGif, displayMedia } from '../../initial_state';
import { isIOS } from '../../utils/is_mobile'; import { isIOS } from '../../utils/is_mobile';
import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../../utils/media_aspect_ratio'; import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../../utils/media_aspect_ratio';
import IconButton from '../icon_button'; import Button from '../button';
const messages = defineMessages({ const messages = defineMessages({
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' }, toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
@ -503,7 +503,7 @@ class MediaGallery extends PureComponent {
)); ));
if (visible) { if (visible) {
spoilerButton = <IconButton title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />; spoilerButton = <Button title={intl.formatMessage(messages.toggle_visible)} icon='eye-slash' overlay onClick={this.handleOpen} />;
} else { } else {
spoilerButton = ( spoilerButton = (
<button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'> <button type='button' onClick={this.handleOpen} className='spoiler-button__overlay'>

View File

@ -5,7 +5,7 @@ import StatusContent from '../status_content';
import Avatar from '../avatar'; import Avatar from '../avatar';
import RelativeTimestamp from '../relative_timestamp'; import RelativeTimestamp from '../relative_timestamp';
import DisplayName from '../display_name'; import DisplayName from '../display_name';
import IconButton from '../icon_button'; import Button from '../button';
export default class ActionsModal extends ImmutablePureComponent { export default class ActionsModal extends ImmutablePureComponent {
@ -32,7 +32,7 @@ export default class ActionsModal extends ImmutablePureComponent {
className={classNames({ active })} className={classNames({ active })}
data-method={isLogout ? 'delete' : null} data-method={isLogout ? 'delete' : null}
> >
{icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />} {icon && <Button title={text} icon={icon} role='presentation' tabIndex='-1' inverted />}
<div> <div>
<div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div> <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div>
<div>{meta}</div> <div>{meta}</div>

View File

@ -0,0 +1,86 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { closeModal } from '../../actions/modal'
import { changeSetting, saveSettings } from '../../actions/settings'
import ModalLayout from './modal_layout'
import Button from '../button'
import SettingSwitch from '../setting_switch'
import Text from '../text'
const messages = defineMessages({
title: { id: 'hashtag_timeline_settings', defaultMessage: 'Hashtag Timeline Settings' },
saveAndClose: { id: 'saveClose', defaultMessage: 'Save & Close' },
onlyMedia: { id: 'community.column_settings.media_only', defaultMessage: 'Media Only' },
showInSidebar: { id: 'show_in_sidebar', defaultMessage: 'Show in Sidebar' },
})
const mapStateToProps = state => ({
settings: state.getIn(['settings', 'community']),
})
const mapDispatchToProps = dispatch => {
return {
onChange(key, checked) {
dispatch(changeSetting(['community', ...key], checked))
},
onSave() {
dispatch(saveSettings())
dispatch(closeModal())
},
}
}
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class HashtagTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
settings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onSave: PropTypes.func.isRequired,
}
handleSaveAndClose = () => {
this.props.onSave()
}
render() {
const { intl, settings, onChange } = this.props
// : todo :
return (
<ModalLayout
width='320'
title={intl.formatMessage(messages.title)}
>
<div className={[_s.default, _s.pb10].join(' ')}>
<SettingSwitch
prefix='community_timeline'
settings={settings}
settingPath={['shows', 'inSidebar']}
onChange={onChange}
label={intl.formatMessage(messages.showInSidebar)}
/>
</div>
<Button
centered
backgroundColor='brand'
color='white'
className={_s.justifyContentCenter}
onClick={this.handleSaveAndClose}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.saveAndClose)}
</Text>
</Button>
</ModalLayout>
)
}
}

View File

@ -5,7 +5,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import Video from '../../features/video'; import Video from '../../features/video';
import ExtendedVideoPlayer from '../extended_video_player'; import ExtendedVideoPlayer from '../extended_video_player';
import IconButton from '../icon_button'; import Button from '../button';
import ImageLoader from '../image_loader'; import ImageLoader from '../image_loader';
import Icon from '../icon'; import Icon from '../icon';
@ -221,7 +221,7 @@ class MediaModal extends ImmutablePureComponent {
</div> </div>
<div className={navigationClassName}> <div className={navigationClassName}>
<IconButton className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} /> <Button className='media-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={40} />
{leftNav} {leftNav}
{rightNav} {rightNav}

View File

@ -23,6 +23,7 @@ import ConfirmationModal from './confirmation_modal'
import GroupCreateModal from './group_create_modal' import GroupCreateModal from './group_create_modal'
import GroupDeleteModal from './group_delete_modal' import GroupDeleteModal from './group_delete_modal'
import GroupEditorModal from './group_editor_modal' import GroupEditorModal from './group_editor_modal'
import HashtagTimelineSettingsModal from './hashtag_timeline_settings_modal'
import HomeTimelineSettingsModal from './home_timeline_settings_modal' import HomeTimelineSettingsModal from './home_timeline_settings_modal'
import HotkeysModal from './hotkeys_modal' import HotkeysModal from './hotkeys_modal'
import ListCreateModal from './list_create_modal' import ListCreateModal from './list_create_modal'
@ -48,6 +49,7 @@ const MODAL_COMPONENTS = {
GROUP_CREATE: () => Promise.resolve({ default: GroupCreateModal }), GROUP_CREATE: () => Promise.resolve({ default: GroupCreateModal }),
GROUP_DELETE: () => Promise.resolve({ default: GroupDeleteModal }), GROUP_DELETE: () => Promise.resolve({ default: GroupDeleteModal }),
GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }), GROUP_EDITOR: () => Promise.resolve({ default: GroupEditorModal }),
HASHTAG_TIMELINE_SETTINGS: () => Promise.resolve({ default: HashtagTimelineSettingsModal }),
HOME_TIMELINE_SETTINGS: () => Promise.resolve({ default: HomeTimelineSettingsModal }), HOME_TIMELINE_SETTINGS: () => Promise.resolve({ default: HomeTimelineSettingsModal }),
HOTKEYS: () => Promise.resolve({ default: HotkeysModal }), HOTKEYS: () => Promise.resolve({ default: HotkeysModal }),
LIST_CREATE: () => Promise.resolve({ default: ListCreateModal }), LIST_CREATE: () => Promise.resolve({ default: ListCreateModal }),

View File

@ -1,59 +0,0 @@
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import Button from '../button'
import Text from '../text'
import ModalLayout from './modal_layout'
const messages = defineMessages({
title: { id: 'promo.gab_pro', defaultMessage: 'Upgrade to GabPRO' },
text: { id: 'pro_upgrade_modal.text', defaultMessage: 'Gab is fully funded by people like you. Please consider supporting us on our mission to defend free expression online for all people.' },
benefits: { id: 'pro_upgrade_modal.benefits', defaultMessage: 'Here are just some of the benefits that thousands of GabPRO members receive:' },
})
export default
@injectIntl
class HomeTimelineSettingsModal extends ImmutablePureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
}
render() {
const { intl } = this.props
return (
<div>
<Text>
{intl.formatMessage(messages.text)}
</Text>
<Text>
{intl.formatMessage(messages.benefits)}
</Text>
<div className={[_s.default, _s.my10].join(' ')}>
<Text> Schedule Posts</Text>
<Text> Get Verified</Text>
<Text> Create Groups</Text>
<Text> Larger Video and Image Uploads</Text>
<Text> Receive the PRO Badge</Text>
<Text> Remove in-feed promotions</Text>
</div>
<Button
centered
backgroundColor='brand'
color='white'
icon='pro'
href='https://pro.gab.com'
className={_s.justifyContentCenter}
iconClassName={[_s.mr5, _s.fillColorWhite].join(' ')}
>
<Text color='inherit' weight='bold' align='center'>
{intl.formatMessage(messages.title)}
</Text>
</Button>
</div>
)
}
}

View File

@ -2,7 +2,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
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 StatusRevisionListContainer from '../../containers/status_revision_list_container' import StatusRevisionListContainer from '../../containers/status_revision_list_container'
import IconButton from '../icon_button' import Button from '../button'
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -28,7 +28,7 @@ class StatusRevisionModal extends ImmutablePureComponent {
<h3 className='status-revisions__header__title'> <h3 className='status-revisions__header__title'>
<FormattedMessage id='status_revisions.heading' defaultMessage='Revision History' /> <FormattedMessage id='status_revisions.heading' defaultMessage='Revision History' />
</h3> </h3>
<IconButton className='status-revisions__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} /> <Button className='status-revisions__close' title={intl.formatMessage(messages.close)} icon='times' onClick={onClose} size={20} />
</div> </div>
<div className='status-revisions__content'> <div className='status-revisions__content'>
<StatusRevisionListContainer id={status.get('id')} /> <StatusRevisionListContainer id={status.get('id')} />

View File

@ -12,7 +12,11 @@ export default class ProgressPanel extends PureComponent {
subtitle="We are 100% funded by you" subtitle="We are 100% funded by you"
hasBackground hasBackground
> >
<ProgressBar progress={monthlyExpensesComplete}/> <ProgressBar
progress={monthlyExpensesComplete}
title={`${Math.min(parseFloat(monthlyExpensesComplete), 100)}% covered this month`}
href='https://shop.dissenter.com/category/donations'
/>
</PanelLayout> </PanelLayout>
) )
} }

View File

@ -1,81 +1,149 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import PopoverLayout from './popover_layout' import PopoverLayout from './popover_layout'
import List from '../list' import List from '../list'
export default class StatusOptionsPopover extends PureComponent { const messages = defineMessages({
_makeMenu = (publicStatus) => { delete: { id: 'status.delete', defaultMessage: 'Delete' },
// const { status, intl: { formatMessage }, withDismiss, withGroupAdmin } = this.props edit: { id: 'status.edit', defaultMessage: 'Edit' },
// const mutingConversation = status.get('muted') mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
reply: { id: 'status.reply', defaultMessage: 'Reply' },
comment: { id: 'status.comment', defaultMessage: 'Comment' },
more: { id: 'status.more', defaultMessage: 'More' },
share: { id: 'status.share', defaultMessage: 'Share' },
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
repost: { id: 'repost', defaultMessage: 'Repost' },
quote: { id: 'status.quote', defaultMessage: 'Quote' },
repost_private: { id: 'status.repost_private', defaultMessage: 'Repost to original audience' },
cancel_repost_private: { id: 'status.cancel_repost_private', defaultMessage: 'Un-repost' },
cannot_repost: { id: 'status.cannot_repost', defaultMessage: 'This post cannot be reposted' },
cannot_quote: { id: 'status.cannot_quote', defaultMessage: 'This post cannot be quoted' },
like: { id: 'status.like', defaultMessage: 'Like' },
open: { id: 'status.open', defaultMessage: 'Expand this status' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
embed: { id: 'status.embed', defaultMessage: 'Embed' },
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
group_remove_account: { id: 'status.remove_account_from_group', defaultMessage: 'Remove account from group' },
group_remove_post: { id: 'status.remove_post_from_group', defaultMessage: 'Remove status from group' },
})
export default
@injectIntl
class StatusOptionsPopover extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
onOpenUnauthorizedModal: PropTypes.func.isRequired,
onOpenStatusSharePopover: PropTypes.func.isRequired,
onReply: PropTypes.func,
onQuote: PropTypes.func,
onFavorite: PropTypes.func,
onRepost: PropTypes.func,
onDelete: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
onBlock: PropTypes.func,
onReport: PropTypes.func,
onEmbed: PropTypes.func,
onMuteConversation: PropTypes.func,
onPin: PropTypes.func,
withDismiss: PropTypes.bool,
withGroupAdmin: PropTypes.bool,
intl: PropTypes.object.isRequired,
}
getItems = () => {
const { status, intl, withDismiss, withGroupAdmin } = this.props
const mutingConversation = status.get('muted')
let menu = []; let menu = [];
// menu.push({ text: formatMessage(messages.open), action: this.handleOpen }); menu.push({
icon: 'circle',
hideArrow: true,
title: formatMessage(messages.open),
onClick: this.handleOpen
});
// if (publicStatus) { if (publicStatus) {
// menu.push({ text: formatMessage(messages.copy), action: this.handleCopy }); menu.push({
// menu.push({ text: formatMessage(messages.embed), action: this.handleEmbed }); icon: 'circle',
// } hideArrow: true,
title: formatMessage(messages.copy),
onClick: this.handleCopy,
})
menu.push({
icon: 'circle',
hideArrow: true,
title: formatMessage(messages.embed),
onClick: this.handleEmbed,
})
}
// if (!me) return menu if (!me) return menu
// menu.push(null); if (status.getIn(['account', 'id']) === me || withDismiss) {
menu.push({
icon: 'circle',
hideArrow: true,
title: formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation),
onClick: this.handleConversationMuteClick,
})
}
// if (status.getIn(['account', 'id']) === me || withDismiss) { if (status.getIn(['account', 'id']) === me) {
// menu.push({ text: formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); if (publicStatus) {
// menu.push(null); menu.push({
// } icon: 'circle',
hideArrow: true,
title: formatMessage(status.get('pinned') ? messages.unpin : messages.pin),
onClick: this.handlePinClick,
})
} else {
if (status.get('visibility') === 'private') {
menu.push({
title: formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private),
onClick: this.handleRepostClick
})
}
}
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({ 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 (status.getIn(['account', 'id']) === me) { if (isStaff) {
// if (publicStatus) { menu.push({ text: formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
// menu.push({ text: formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); menu.push({ text: formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
// } else { }
// if (status.get('visibility') === 'private') {
// menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_repost_private : messages.repost_private), action: this.handleRepostClick });
// }
// }
// 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) { if (withGroupAdmin) {
// menu.push(null); menu.push({ text: formatMessage(messages.group_remove_account), action: this.handleGroupRemoveAccount });
// menu.push({ text: formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` }); menu.push({ text: formatMessage(messages.group_remove_post), action: this.handleGroupRemovePost });
// 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; return menu;
} }
render() { render() {
const items = this.getItems()
return ( return (
<PopoverLayout className={_s.width240PX}> <PopoverLayout className={_s.width240PX}>
<List <List
scrollKey='profile_options' scrollKey='profile_options'
items={[ items={items}
{
title: 'Help',
href: 'https://help.gab.com',
},
{
title: 'Settings',
href: '/settings',
},
{
title: 'Log Out',
href: '/auth/log_out',
},
]}
small small
/> />
</PopoverLayout> </PopoverLayout>

View File

@ -3,12 +3,12 @@ import spring from 'react-motion/lib/spring'
import detectPassiveEvents from 'detect-passive-events' import detectPassiveEvents from 'detect-passive-events'
import classNames from 'classnames' import classNames from 'classnames'
import Overlay from 'react-overlays/lib/Overlay' import Overlay from 'react-overlays/lib/Overlay'
import { changeComposeVisibility } from '../../../actions/compose' import { changeComposeVisibility } from '../../actions/compose'
import { openModal, closeModal } from '../../../actions/modal' import { openModal, closeModal } from '../../actions/modal'
import { isUserTouching } from '../../../utils/is_mobile' import { isUserTouching } from '../../utils/is_mobile'
import Motion from '../../ui/util/optional_motion' import Motion from '../../features/ui/util/optional_motion'
import Icon from '../../../components/icon' import Icon from '../icon'
import ComposeExtraButton from './compose_extra_button' import ComposeExtraButton from '../../features/compose/components/compose_extra_button'
const messages = defineMessages({ const messages = defineMessages({
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
@ -246,25 +246,13 @@ class PrivacyDropdown extends PureComponent {
const valueOption = this.options.find(item => item.value === value) const valueOption = this.options.find(item => item.value === value)
return ( return (
<div className={classNames('privacy-dropdown', placement, { active: open })} onKeyDown={this.handleKeyDown}> <PrivacyDropdownMenu
<div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}> items={this.options}
<ComposeExtraButton value={value}
icon={valueOption.icon} onClose={this.handleClose}
title={intl.formatMessage(messages.visibility)} onChange={this.handleChange}
onClick={this.handleToggle} placement={placement}
/> />
</div>
<Overlay show={open} placement={placement} target={this}>
<PrivacyDropdownMenu
items={this.options}
value={value}
onClose={this.handleClose}
onChange={this.handleChange}
placement={placement}
/>
</Overlay>
</div>
) )
} }

View File

@ -105,14 +105,11 @@ class ProfileHeader extends ImmutablePureComponent {
this.props.onFollow(this.props.account) this.props.onFollow(this.props.account)
} }
handleUnrequest = () => {
//
}
handleBlock = () => { handleBlock = () => {
// this.props.onBlock(this.props.account) this.props.onBlock(this.props.account)
} }
// : todo :
makeInfo() { makeInfo() {
const { account, intl } = this.props; const { account, intl } = this.props;
@ -135,28 +132,6 @@ class ProfileHeader extends ImmutablePureComponent {
return info; return info;
}; };
getActionBtn() {
const { account, intl } = this.props;
let actionBtn = null;
if (!account || !me) return actionBtn;
if (me !== account.get('id')) {
if (!account.get('relationship')) { // Wait until the relationship is loaded
//
} else if (account.getIn(['relationship', 'requested'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.requested)} onClick={this.props.onFollow} />;
} else if (!account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button disabled={account.getIn(['relationship', 'blocked_by'])} className={classNames('logo-button', { 'button--destructive': account.getIn(['relationship', 'following']) })} text={intl.formatMessage(account.getIn(['relationship', 'following']) ? messages.unfollow : messages.follow)} onClick={this.props.onFollow} />;
} else if (account.getIn(['relationship', 'blocking'])) {
actionBtn = <Button className='logo-button' text={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.props.onBlock} />;
}
}
return actionBtn
}
setOpenMoreNodeRef = (n) => { setOpenMoreNodeRef = (n) => {
this.openMoreNode = n this.openMoreNode = n
} }
@ -195,37 +170,78 @@ class ProfileHeader extends ImmutablePureComponent {
const avatarSize = headerMissing ? '75' : '150' const avatarSize = headerMissing ? '75' : '150'
let buttonText; let buttonText = ''
let buttonOptions; let buttonOptions = {}
if (!account) { if (!account) {
console.log("no account")
} }
else { else {
if (account.get('id') !== me && account.get('relationship', null) !== null) { if (!account.get('relationship')) {
const following = account.getIn(['relationship', 'following']) console.log("no relationship")
const requested = account.getIn(['relationship', 'requested']) // Wait until the relationship is loaded
const blocking = account.getIn(['relationship', 'blocking']) } else if (account.getIn(['relationship', 'requested'])) {
buttonText = intl.formatMessage(messages.requested)
if (requested || blocking) { buttonOptions = {
buttonText = intl.formatMessage(requested ? messages.requested : messages.unblock) narrow: true,
buttonOptions = { onClick: this.handleFollow,
narrow: true, color: 'primary',
onClick: requested ? this.handleUnrequest : this.handleBlock, backgroundColor: 'tertiary',
color: 'primary',
backgroundColor: 'tertiary',
}
} else if (!account.get('moved') || following) {
buttonOptions = {
narrow: true,
outline: !following,
color: !following ? 'brand' : 'white',
backgroundColor: !following ? 'none' : 'brand',
onClick: this.handleFollow,
}
buttonText = intl.formatMessage(following ? messages.unfollow : messages.follow)
} }
} else if (!account.getIn(['relationship', 'blocking'])) {
const isFollowing = account.getIn(['relationship', 'following'])
const isBlockedBy = account.getIn(['relationship', 'blocked_by'])
buttonText = intl.formatMessage(isFollowing ? messages.unfollow : messages.follow)
buttonOptions = {
narrow: true,
onClick: this.handleFollow,
color: 'primary',
backgroundColor: 'tertiary',
disabled: isBlockedBy,
}
} else if (account.getIn(['relationship', 'blocking'])) {
buttonText = intl.formatMessage(messages.unblock)
buttonOptions = {
narrow: true,
onClick: this.handleBlock,
color: 'primary',
backgroundColor: 'tertiary',
}
} else {
console.log("no nothin")
} }
// if (account.get('id') !== me && account.get('relationship', null) !== null) {
// const following = account.getIn(['relationship', 'following'])
// const requested = account.getIn(['relationship', 'requested'])
// const blocking = account.getIn(['relationship', 'blocking'])
// if (requested || blocking) {
// buttonText = intl.formatMessage(requested ? messages.requested : messages.unblock)
// buttonOptions = {
// narrow: true,
// onClick: requested ? this.handleFollow : this.handleBlock,
// color: 'primary',
// backgroundColor: 'tertiary',
// }
// } else if (!account.get('moved') || following) {
// buttonOptions = {
// narrow: true,
// outline: !following,
// color: !following ? 'brand' : 'white',
// backgroundColor: !following ? 'none' : 'brand',
// onClick: this.handleFollow,
// }
// buttonText = intl.formatMessage(following ? messages.unfollow : messages.follow)
// } else {
// console.log("SHOW ELSE")
// }
// }
} }
console.log("buttonOptions:", buttonText, buttonOptions)
return ( return (
<div className={[_s.default, _s.z1, _s.width100PC].join(' ')}> <div className={[_s.default, _s.z1, _s.width100PC].join(' ')}>
@ -287,7 +303,7 @@ class ProfileHeader extends ImmutablePureComponent {
icon='ellipsis' icon='ellipsis'
iconWidth='18px' iconWidth='18px'
iconHeight='18px' iconHeight='18px'
iconClassName={_s.fillColorBrand} iconClassName={_s.inheritFill}
color='brand' color='brand'
backgroundColor='none' backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')} className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}
@ -302,7 +318,7 @@ class ProfileHeader extends ImmutablePureComponent {
icon='chat' icon='chat'
iconWidth='18px' iconWidth='18px'
iconHeight='18px' iconHeight='18px'
iconClassName={_s.fillColorBrand} iconClassName={_s.inheritFill}
color='brand' color='brand'
backgroundColor='none' backgroundColor='none'
className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')} className={[_s.justifyContentCenter, _s.alignItemsCenter, _s.mr10, _s.px10].join(' ')}

View File

@ -1,28 +1,59 @@
import classNames from 'classnames/bind'
import Button from './button'
import Text from './text' import Text from './text'
const cx = classNames.bind(_s)
export default class ProgressBar extends PureComponent { export default class ProgressBar extends PureComponent {
static propTypes = { static propTypes = {
progress: PropTypes.number, progress: PropTypes.number,
small: PropTypes.bool,
title: PropTypes.string,
} }
render() { render() {
const { progress } = this.props const {
progress,
small,
title,
href
} = this.props
const completed = Math.min(parseFloat(progress), 100) const completed = Math.min(parseFloat(progress), 100)
const style = { const style = {
width: `${completed}%`, width: `${completed}%`,
} }
const title = `${completed}% covered this month`
const containerOptions = {
href,
className: cx({
default: 1,
backgroundPanel: !small,
backgroundSubtle2: small,
noUnderline: 1,
circle: 1,
overflowHidden: 1,
cursorPointer: 1,
height22PX: !small,
height4PX: small,
}),
}
return ( return (
<a href='https://shop.dissenter.com/category/donations' className={[_s.default, _s.backgroundPanel, _s.noUnderline, _s.circle, _s.overflowHidden, _s.height22PX, _s.cursorPointer].join(' ')}> <Button
<div className={[_s.default, _s.backgroundColorBrandDark, _s.circle, _s.height22PX].join(' ')} style={style} /> noClasses
<div className={[_s.default, _s.positionAbsolute, _s.width100PC, _s.height22PX, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}> {...containerOptions}
<Text size='small' weight='bold' color='white'> >
{title} <div className={[_s.default, _s.backgroundColorBrandDark, _s.circle, _s.height100PC].join(' ')} style={style} />
</Text> <div className={[_s.default, _s.positionAbsolute, _s.width100PC, _s.height100PC, _s.alignItemsCenter, _s.justifyContentCenter].join(' ')}>
{
!!title &&
<Text size='small' weight='bold' color='white'>
{title}
</Text>
}
</div> </div>
</a> </Button>
) )
} }
} }

View File

@ -149,7 +149,7 @@ class StatusActionBar extends ImmutablePureComponent {
const favoriteCount = status.get('favourites_count') // : todo : const favoriteCount = status.get('favourites_count') // : todo :
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && ( const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} /> <Button className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
) )
const hasInteractions = favoriteCount > 0 || replyCount > 0 || repostCount > 0 const hasInteractions = favoriteCount > 0 || replyCount > 0 || repostCount > 0
@ -189,7 +189,7 @@ class StatusActionBar extends ImmutablePureComponent {
{ {
favoriteCount > 0 && favoriteCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses}>
<Text color='secondary'> <Text color='secondary' size='small'>
{favoriteCount} {favoriteCount}
&nbsp;Likes &nbsp;Likes
</Text> </Text>
@ -198,7 +198,7 @@ class StatusActionBar extends ImmutablePureComponent {
{ {
replyCount > 0 && replyCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses}>
<Text color='secondary'> <Text color='secondary' size='small'>
{replyCount} {replyCount}
&nbsp;Comments &nbsp;Comments
</Text> </Text>
@ -207,7 +207,7 @@ class StatusActionBar extends ImmutablePureComponent {
{ {
repostCount > 0 && repostCount > 0 &&
<button className={interactionBtnClasses}> <button className={interactionBtnClasses}>
<Text color='secondary'> <Text color='secondary' size='small'>
{repostCount} {repostCount}
&nbsp;Reposts &nbsp;Reposts
</Text> </Text>

View File

@ -1,13 +1,14 @@
import { Fragment } from 'react'; 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 { FormattedMessage, injectIntl, defineMessages } from 'react-intl'; import { FormattedMessage, injectIntl, defineMessages } from 'react-intl'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { isRtl } from '../../utils/rtl'; import { isRtl } from '../../utils/rtl'
import Button from '../button' import Button from '../button'
import Icon from '../icon'; import Icon from '../icon'
import Text from '../text'
const MAX_HEIGHT = 200; const MAX_HEIGHT = 200
const messages = defineMessages({ const messages = defineMessages({
showMore: { id: 'status.show_more', defaultMessage: 'Show more' }, showMore: { id: 'status.show_more', defaultMessage: 'Show more' },
@ -23,7 +24,7 @@ class StatusContent extends ImmutablePureComponent {
static contextTypes = { static contextTypes = {
router: PropTypes.object, router: PropTypes.object,
}; }
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map.isRequired, status: ImmutablePropTypes.map.isRequired,
@ -34,36 +35,36 @@ class StatusContent extends ImmutablePureComponent {
collapsable: PropTypes.bool, collapsable: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
isComment: PropTypes.bool, isComment: PropTypes.bool,
}; }
state = { state = {
hidden: true, hidden: true,
collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't). collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't).
}; }
_updateStatusLinks () { _updateStatusLinks() {
const node = this.node; const node = this.node
if (!node) return; if (!node) return
const links = node.querySelectorAll('a'); const links = node.querySelectorAll('a')
for (var i = 0; i < links.length; ++i) { for (var i = 0; i < links.length; ++i) {
let link = links[i]; let link = links[i]
if (link.classList.contains('status-link')) { if (link.classList.contains('status-link')) {
continue; continue
} }
link.classList.add('status-link'); link.classList.add('status-link')
let mention = this.props.status.get('mentions').find(item => link.href === `${item.get('url')}`); let mention = this.props.status.get('mentions').find(item => link.href === `${item.get('url')}`)
if (mention) { if (mention) {
link.addEventListener('click', this.onMentionClick.bind(this, mention), false); link.addEventListener('click', this.onMentionClick.bind(this, mention), false)
link.setAttribute('title', mention.get('acct')); link.setAttribute('title', mention.get('acct'))
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) { } else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false); link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false)
} else { } else {
link.setAttribute('title', link.href); link.setAttribute('title', link.href)
} }
} }
@ -74,59 +75,59 @@ class StatusContent extends ImmutablePureComponent {
&& node.clientHeight > MAX_HEIGHT && node.clientHeight > MAX_HEIGHT
&& this.props.status.get('spoiler_text').length === 0 && this.props.status.get('spoiler_text').length === 0
) { ) {
this.setState({ collapsed: true }); this.setState({ collapsed: true })
} }
} }
componentDidMount () { componentDidMount() {
this._updateStatusLinks(); this._updateStatusLinks()
} }
componentDidUpdate () { componentDidUpdate() {
this._updateStatusLinks(); this._updateStatusLinks()
} }
onMentionClick = (mention, e) => { onMentionClick = (mention, e) => {
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault()
this.context.router.history.push(`/${mention.get('acct')}`); this.context.router.history.push(`/${mention.get('acct')}`)
} }
} }
onHashtagClick = (hashtag, e) => { onHashtagClick = (hashtag, e) => {
hashtag = hashtag.replace(/^#/, '').toLowerCase(); hashtag = hashtag.replace(/^#/, '').toLowerCase()
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) { if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
e.preventDefault(); e.preventDefault()
this.context.router.history.push(`/tags/${hashtag}`); this.context.router.history.push(`/tags/${hashtag}`)
} }
} }
handleMouseDown = (e) => { handleMouseDown = (e) => {
this.startXY = [e.clientX, e.clientY]; this.startXY = [e.clientX, e.clientY]
} }
handleMouseUp = (e) => { handleMouseUp = (e) => {
if (!this.startXY) return; if (!this.startXY) return
const [ startX, startY ] = this.startXY; const [startX, startY] = this.startXY
const [ deltaX, deltaY ] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]; const [deltaX, deltaY] = [Math.abs(e.clientX - startX), Math.abs(e.clientY - startY)]
if (e.target.localName === 'button' || if (e.target.localName === 'button' ||
e.target.localName === 'a' || e.target.localName === 'a' ||
(e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) { (e.target.parentNode && (e.target.parentNode.localName === 'button' || e.target.parentNode.localName === 'a'))) {
return; return
} }
if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) { if (deltaX + deltaY < 5 && e.button === 0 && this.props.onClick) {
this.props.onClick(); this.props.onClick()
} }
this.startXY = null; this.startXY = null
} }
handleSpoilerClick = (e) => { handleSpoilerClick = (e) => {
e.preventDefault(); e.preventDefault()
if (this.props.onExpandedToggle) { if (this.props.onExpandedToggle) {
// The parent manages the state // The parent manages the state
@ -137,77 +138,125 @@ class StatusContent extends ImmutablePureComponent {
} }
handleReadMore = (e) => { handleReadMore = (e) => {
e.preventDefault(); e.preventDefault()
this.setState({ collapsed: false }); this.setState({ collapsed: false })
} }
setRef = (c) => { setRef = (c) => {
this.node = c; this.node = c
} }
getHtmlContent = () => { getHtmlContent = () => {
const { status, reblogContent } = this.props; const { status, reblogContent } = this.props
const properContent = status.get('contentHtml'); const properContent = status.get('contentHtml')
return reblogContent return reblogContent
? `${reblogContent} <div class='status__quote'>${properContent}</div>` ? `${reblogContent} <div class='status__quote'>${properContent}</div>`
: properContent; : properContent
} }
render () { render() {
const { status, intl, isComment } = this.props const { status, intl, isComment } = this.props
const { collapsed } = this.state const { collapsed } = this.state
console.log("status content", status.get('content')) if (status.get('content').length === 0) return null
if (status.get('content').length === 0) return null; const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden; const content = { __html: this.getHtmlContent() }
const spoilerContent = { __html: status.get('spoilerHtml') }
const content = { __html: this.getHtmlContent() }; const directionStyle = {
const spoilerContent = { __html: status.get('spoilerHtml') }; direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr',
const directionStyle = { direction: 'ltr' };
// const classNames = '';
// classnames('status__content', {
// 'status__content--with-action': this.props.onClick && this.context.router,
// 'status__content--with-spoiler': status.get('spoiler_text').length > 0,
// 'status__content--collapsed': this.state.collapsed === true,
// // _s.px15, _s.mb15
// });
if (isRtl(status.get('search_index'))) {
directionStyle.direction = 'rtl';
} }
if (status.get('spoiler_text').length > 0) { if (status.get('spoiler_text').length > 0) {
let mentionsPlaceholder = ''; let mentionsPlaceholder = null
const mentionLinks = status.get('mentions').map(item => ( const mentionLinks = status.get('mentions').map(item => (
<Button to={`/${item.get('acct')}`} href={`/${item.get('acct')}`} key={item.get('id')} className='mention'> <Button
@<span>{item.get('username')}</span> text
backgroundColor='none'
to={`/${item.get('acct')}`}
href={`/${item.get('acct')}`}
key={item.get('id')}
className={['mention', _s.mr5, _s.mb5].join(' ')}
>
@{item.get('username')}
</Button> </Button>
)).reduce((aggregate, item) => [...aggregate, item, ' '], []); )).reduce((aggregate, item) => [...aggregate, item, ' '], [])
const toggleText = intl.formatMessage(hidden ? messages.showMore : messages.showLess);
if (hidden) { if (hidden) {
mentionsPlaceholder = <div>{mentionLinks}</div>; mentionsPlaceholder = (
<div className={[_s.statusContent, _s.default, _s.alignItemsStart, _s.flexRow, _s.flexWrap].join(' ')}>
{mentionLinks}
</div>
)
} }
const toggleText = intl.formatMessage(hidden ? messages.showMore : messages.showLess)
const spoilerContainerClasses = cx({
default: 1,
py10: 1,
borderBottom1PX: !hidden,
borderColorSecondary: !hidden,
mb10: !hidden,
})
const statusContentClasses = cx({
statusContent: 1,
displayNone: hidden,
})
return ( return (
<div className={[].join(' ')} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}> <div
<p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}> className={[].join(' ')}
<span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} /> ref={this.setRef}
{' '} tabIndex='0'
<button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button> className={[_s.px15, _s.statusContent].join(' ')}
</p> style={directionStyle}
onMouseDown={this.handleMouseDown}
onMouseUp={this.handleMouseUp}
>
<div className={spoilerContainerClasses}>
<div className={[_s.default, _s.flexRow, _s.mr5].join(' ')}>
<Icon id='warning' height='14px' width='14px' className={[_s.fillColorBlack, _s.mt2, _s.mr5].join(' ')}/>
<div
className={_s.statusContent}
dangerouslySetInnerHTML={spoilerContent}
lang={status.get('language')}
/>
</div>
<div className={[_s.default, _s.mt10, _s.alignItemsStart].join(' ')}>
<Button
narrow
radiusSmall
backgroundColor='tertiary'
color='primary'
tabIndex='0'
onClick={this.handleSpoilerClick}
>
<Text size='small' color='inherit'>
{toggleText}
</Text>
</Button>
</div>
</div>
{mentionsPlaceholder} {mentionsPlaceholder}
<div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} /> <div
tabIndex={!hidden ? 0 : null}
className={statusContentClasses}
style={directionStyle}
dangerouslySetInnerHTML={content}
lang={status.get('language')}
/>
</div> </div>
); )
} else if (this.props.onClick) { } else if (this.props.onClick) {
const hasMarginBottom = !!status.get('card') || !!status.get('poll') || status.get('media_attachments').size > 0 const hasMarginBottom = !!status.get('card') || !!status.get('poll') || status.get('media_attachments').size > 0
@ -256,7 +305,7 @@ class StatusContent extends ImmutablePureComponent {
dangerouslySetInnerHTML={content} dangerouslySetInnerHTML={content}
lang={status.get('language')} lang={status.get('language')}
/> />
); )
} }
} }

View File

@ -12,24 +12,30 @@ import Icon from './icon'
import Button from './button' import Button from './button'
import Avatar from './avatar' import Avatar from './avatar'
const mapDispatchToProps = (dispatch) => ({
onOpenStatusOptionsPopover(targetRef, status) {
dispatch(openPopover('STATUS_OPTIONS', {
targetRef,
status,
position: 'top',
}))
},
})
export default export default
@connect(null, null) @connect(null, mapDispatchToProps)
class StatusHeader extends ImmutablePureComponent { class StatusHeader extends ImmutablePureComponent {
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,
onOpenStatusOptionsPopover: PropTypes.func.isRequired,
} }
handleStatusOptionsClick() { handleOpenStatusOptionsPopover = () => {
console.log("handleStatusOptionsClick:", this.props) this.props.onOpenStatusOptionsPopover(this.statusOptionsButton, this.props.status)
this.props.dispatch(openPopover('STATUS_OPTIONS', {
targetRef: this.statusOptionsButton,
position: 'top',
status: this.props.status,
}))
} }
handleOpenStatusEdits() { handleOpenStatusEdits = () => {
// : todo : // : todo :
this.props.dispatch(openPopover('REPOST', { this.props.dispatch(openPopover('REPOST', {
targetRef: this.statusOptionsButton, targetRef: this.statusOptionsButton,
@ -150,7 +156,7 @@ class StatusHeader extends ImmutablePureComponent {
iconHeight='20px' iconHeight='20px'
iconClassName={_s.fillColorSecondary} iconClassName={_s.fillColorSecondary}
className={_s.marginLeftAuto} className={_s.marginLeftAuto}
onClick={this.handleStatusOptionsClick} onClick={this.handleOpenStatusOptionsPopover}
buttonRef={this.setStatusOptionsButton} buttonRef={this.setStatusOptionsButton}
/> />
</div> </div>

View File

@ -54,7 +54,7 @@ export default class Switch extends PureComponent {
return ( return (
<div className={[_s.default, _s.flexRow, _s.py5, _s.alignItemsCenter].join(' ')}> <div className={[_s.default, _s.flexRow, _s.py5, _s.alignItemsCenter].join(' ')}>
<Text {...labelProps}> <Text {...labelProps} className={_s.mr10}>
{label} {label}
</Text> </Text>

View File

@ -1,5 +1,7 @@
import { NavLink, withRouter } from 'react-router-dom' import { withRouter } from 'react-router-dom'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Button from './button'
import Icon from './icon'
import Text from './text' import Text from './text'
const cx = classNames.bind(_s) const cx = classNames.bind(_s)
@ -10,8 +12,11 @@ class TabBarItem extends PureComponent {
static propTypes = { static propTypes = {
location: PropTypes.object.isRequired, location: PropTypes.object.isRequired,
title: PropTypes.string, title: PropTypes.string,
onClick: PropTypes.func,
icon: PropTypes.string,
to: PropTypes.string, to: PropTypes.string,
large: PropTypes.bool, large: PropTypes.bool,
active: PropTypes.bool,
} }
state = { state = {
@ -31,7 +36,15 @@ class TabBarItem extends PureComponent {
} }
render() { render() {
const { title, to, location, large } = this.props const {
title,
to,
onClick,
location,
large,
icon,
// active
} = this.props
const { active } = this.state const { active } = this.state
const isCurrent = active === -1 ? to === location.pathname : active const isCurrent = active === -1 ? to === location.pathname : active
@ -46,6 +59,7 @@ class TabBarItem extends PureComponent {
justifyContentCenter: 1, justifyContentCenter: 1,
borderBottom2PX: 1, borderBottom2PX: 1,
py5: 1, py5: 1,
backgroundTransparent: 1,
borderColorTransparent: !isCurrent, borderColorTransparent: !isCurrent,
borderColorBrand: isCurrent, borderColorBrand: isCurrent,
mr5: large, mr5: large,
@ -68,14 +82,31 @@ class TabBarItem extends PureComponent {
weight: isCurrent ? 'bold' : large ? 'medium' : 'normal', weight: isCurrent ? 'bold' : large ? 'medium' : 'normal',
} }
const iconOptions = {
id: icon,
width: !!large ? 20 : 14,
height: !!large ? 20 : 14,
}
return ( return (
<NavLink to={to} className={containerClasses}> <Button
onClick={onClick}
to={to}
className={containerClasses}
noClasses
>
<span className={textParentClasses}> <span className={textParentClasses}>
{ !!title &&
<Text {...textOptions}> <Text {...textOptions}>
{title} {title}
</Text> </Text>
}
{ !!icon &&
<Icon {...iconOptions} />
}
</span> </span>
</NavLink> </Button>
) )
} }
} }

View File

@ -42,6 +42,7 @@ export default class Text extends PureComponent {
weight: PropTypes.oneOf(Object.keys(WEIGHTS)), weight: PropTypes.oneOf(Object.keys(WEIGHTS)),
align: PropTypes.oneOf(Object.keys(ALIGNMENTS)), align: PropTypes.oneOf(Object.keys(ALIGNMENTS)),
underline: PropTypes.bool, underline: PropTypes.bool,
htmlFor: PropTypes.string,
} }
static defaultProps = { static defaultProps = {
@ -60,7 +61,8 @@ export default class Text extends PureComponent {
size, size,
weight, weight,
underline, underline,
align align,
htmlFor
} = this.props } = this.props
const classes = cx(className, { const classes = cx(className, {
@ -93,6 +95,7 @@ export default class Text extends PureComponent {
return React.createElement( return React.createElement(
tagName, tagName,
{ {
htmlFor,
className: classes, className: classes,
}, },
children, children,

View File

@ -1,62 +0,0 @@
import { defineMessages, injectIntl } from 'react-intl';
import { openModal } from '../../../../actions/modal';
import { meUsername } from '../../../../initial_state';
const messages = defineMessages({
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' },
keyboard_shortcuts: { id: 'navigation_bar.keyboard_shortcuts', defaultMessage: 'Hotkeys' },
});
const mapDispatchToProps = (dispatch) => ({
onOpenHotkeys() {
dispatch(openModal('HOTKEYS'));
},
});
class ActionBar extends PureComponent {
static propTypes = {
intl: PropTypes.object.isRequired,
size: PropTypes.number,
};
handleHotkeyClick = () => {
this.props.onOpenHotkeys();
}
render () {
const { intl } = this.props;
const size = this.props.size || 16;
let menu = [];
menu.push({ text: intl.formatMessage(messages.profile), to: `/${meUsername}` });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });
menu.push({ text: intl.formatMessage(messages.blocks), to: '/blocks' });
menu.push({ text: intl.formatMessage(messages.domain_blocks), to: '/domain_blocks' });
menu.push({ text: intl.formatMessage(messages.filters), href: '/filters' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.keyboard_shortcuts), action: this.handleHotkeyClick });
menu.push({ text: intl.formatMessage(messages.preferences), href: '/settings/preferences' });
menu.push({ text: intl.formatMessage(messages.logout), href: '/auth/sign_out', isLogout: true });
return (
<div style={{'marginTop':'-6px'}}>
<div>
{ /* <DropdownMenuContainer items={menu} icon='chevron-down' size={size} direction='right' /> */ }
</div>
</div>
);
}
}
export default injectIntl(connect(null, mapDispatchToProps)(ActionBar));

View File

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

View File

@ -1,16 +0,0 @@
.character-counter {
cursor: default;
font-family: $font-sans-serif, sans-serif;
color: $gab-secondary-text;
@include text-sizing(14px, 600);
&--over {
color: $warning-red;
}
&__wrapper {
align-self: center;
margin-right: 4px;
}
}

View File

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

View File

@ -1,5 +1,5 @@
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import Icon from '../../../components/icon' import Button from '../../../components/button'
const cx = classNames.bind(_s) const cx = classNames.bind(_s)
@ -13,18 +13,6 @@ export default class ComposeExtraButton extends PureComponent {
active: PropTypes.bool, active: PropTypes.bool,
} }
state = {
hovering: false,
}
handleOnMouseEnter = () => {
this.setState({ hovering: true })
}
handleOnMouseLeave = () => {
this.setState({ hovering: false })
}
render() { render() {
const { const {
title, title,
@ -35,7 +23,6 @@ export default class ComposeExtraButton extends PureComponent {
small, small,
active active
} = this.props } = this.props
const { hovering } = this.state
const containerClasses = cx({ const containerClasses = cx({
default: 1, default: 1,
@ -44,13 +31,6 @@ export default class ComposeExtraButton extends PureComponent {
}) })
const btnClasses = cx({ const btnClasses = cx({
default: 1,
circle: 1,
flexRow: 1,
cursorPointer: 1,
outlineNone: 1,
backgroundSubtle: !hovering && !active,
backgroundSubtle2: hovering && !active,
backgroundColorBrandLight: active, backgroundColorBrandLight: active,
py10: !small, py10: !small,
px10: !small, px10: !small,
@ -58,18 +38,6 @@ export default class ComposeExtraButton extends PureComponent {
px5: small, px5: small,
}) })
const titleClasses = cx({
default: 1,
ml5: 1,
text: 1,
lineHeight15: 1,
fontSize12PX: 1,
fontWeightMedium: 1,
colorSecondary: !active,
colorWhite: active,
displayNone: !hovering,
})
const iconClasses = cx({ const iconClasses = cx({
fillColorSecondary: !active, fillColorSecondary: !active,
fillColorWhite: active, fillColorWhite: active,
@ -78,23 +46,18 @@ export default class ComposeExtraButton extends PureComponent {
const iconSize = !!small ? '12px' : '18px' const iconSize = !!small ? '12px' : '18px'
return ( return (
<div className={containerClasses}> <div className={containerClasses} data-tip={title}>
<button <Button
className={btnClasses} className={btnClasses}
title={title} title={title}
disabled={disabled} disabled={disabled}
onClick={onClick} onClick={onClick}
onMouseEnter={() => this.handleOnMouseEnter()} backgroundColor='secondary'
onMouseLeave={() => this.handleOnMouseLeave()} iconClassName={iconClasses}
> icon={icon}
<Icon id={icon} width={iconSize} height={iconSize} className={iconClasses} /> iconWidth={iconSize}
{ iconHeight={iconSize}
(!small && !!title) && />
<span className={titleClasses}>
{title}
</span>
}
</button>
{children} {children}
</div> </div>
) )

View File

@ -10,7 +10,7 @@ import AutosuggestTextbox from '../../../../components/autosuggest_textbox';
import PollButton from '../../components/poll_button'; import PollButton from '../../components/poll_button';
import UploadButton from '../../components/upload_button'; import UploadButton from '../../components/upload_button';
import SpoilerButton from '../../components/spoiler_button'; import SpoilerButton from '../../components/spoiler_button';
import PrivacyDropdown from '../../components/privacy_dropdown'; import PostPrivacyButton from '../../../../components/post_privacy_button';
import EmojiPickerButton from '../../components/emoji_picker_button' import EmojiPickerButton from '../../components/emoji_picker_button'
import EmojiPickerDropdown from '../../containers/emoji_picker_dropdown_container'; import EmojiPickerDropdown from '../../containers/emoji_picker_dropdown_container';
import PollFormContainer from '../../containers/poll_form_container'; import PollFormContainer from '../../containers/poll_form_container';
@ -230,7 +230,8 @@ class ComposeForm extends ImmutablePureComponent {
quoteOfId, quoteOfId,
edit, edit,
scheduledAt, scheduledAt,
spoiler spoiler,
replyToId
} = this.props } = this.props
const disabled = this.props.isSubmitting; const disabled = this.props.isSubmitting;
const text = [this.props.spoilerText, countableText(this.props.text)].join(''); const text = [this.props.spoilerText, countableText(this.props.text)].join('');
@ -239,7 +240,7 @@ class ComposeForm extends ImmutablePureComponent {
const containerClasses = cx({ const containerClasses = cx({
default: 1, default: 1,
flexGrow1: 1, flexNormal: 1,
flexRow: shouldCondense, flexRow: shouldCondense,
radiusSmall: shouldCondense, radiusSmall: shouldCondense,
backgroundSubtle: shouldCondense, backgroundSubtle: shouldCondense,
@ -326,10 +327,10 @@ class ComposeForm extends ImmutablePureComponent {
> >
<div className='compose-form__modifiers'> <div className='compose-form__modifiers'>
<UploadForm /> <UploadForm replyToId={replyToId} />
{ {
!edit && !edit &&
<PollFormContainer /> <PollFormContainer replyToId={replyToId} />
} }
</div> </div>
@ -345,7 +346,7 @@ class ComposeForm extends ImmutablePureComponent {
} }
{ {
!shouldCondense && !shouldCondense &&
<PrivacyDropdown /> <PostPrivacyButton />
} }
<SpoilerButton small={shouldCondense} /> <SpoilerButton small={shouldCondense} />
<SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} /> <SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} />

View File

@ -1,10 +1,8 @@
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 { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import ActionBar from '../action_bar';
import Avatar from '../../../../components/avatar'; import Avatar from '../../../../components/avatar';
import Button from '../../../../components/button' import Button from '../../../../components/button'
import IconButton from '../../../../components/icon_button';
import { me } from '../../../../initial_state'; import { me } from '../../../../initial_state';
const mapStateToProps = state => { const mapStateToProps = state => {
@ -43,8 +41,7 @@ class NavigationBar extends ImmutablePureComponent {
</div> </div>
<div className='navigation-bar__actions'> <div className='navigation-bar__actions'>
<IconButton className='close' title='' icon='close' onClick={this.props.onClose} /> <Button className='close' title='' icon='close' onClick={this.props.onClose} />
<ActionBar account={account} />
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,65 @@
import { defineMessages, injectIntl } from 'react-intl'
import { addPoll, removePoll } from '../../../actions/compose'
import ComposeExtraButton from './compose_extra_button'
const messages = defineMessages({
add_poll: { id: 'poll_button.add_poll', defaultMessage: 'Add poll' },
title: { id: 'poll_button.title', defaultMessage: 'Poll' },
remove_poll: { id: 'poll_button.remove_poll', defaultMessage: 'Remove poll' },
})
const mapStateToProps = state => ({
unavailable: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
active: state.getIn(['compose', 'poll']) !== null,
})
const mapDispatchToProps = dispatch => ({
onClick() {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
dispatch(removePoll())
} else {
dispatch(addPoll())
}
})
},
})
export default
@connect(mapStateToProps, mapDispatchToProps)
@injectIntl
class PollButton extends PureComponent {
static propTypes = {
disabled: PropTypes.bool,
unavailable: PropTypes.bool,
active: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
small: PropTypes.bool,
}
handleClick = () => {
this.props.onClick()
}
render() {
const { intl, active, unavailable, disabled, small } = this.props
if (unavailable) return null
return (
<ComposeExtraButton
title={intl.formatMessage(active ? messages.remove_poll : messages.title)}
disabled={disabled}
onClick={this.handleClick}
icon='poll'
small={small}
active={active}
/>
)
}
}

View File

@ -2,6 +2,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import StatusContent from '../../../components/status_content'; import StatusContent from '../../../components/status_content';
// : todo : do we need this? make work inside of status/status content
export default class QuotedStatusPreview extends PureComponent { export default class QuotedStatusPreview extends PureComponent {
static propTypes = { static propTypes = {
status: ImmutablePropTypes.map, status: ImmutablePropTypes.map,

View File

@ -3,7 +3,7 @@ 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 { NavLink } from 'react-router-dom';
import Avatar from '../../../../components/avatar'; import Avatar from '../../../../components/avatar';
import IconButton from '../../../../components/icon_button'; import Button from '../../../../components/button';
import DisplayName from '../../../../components/display_name'; import DisplayName from '../../../../components/display_name';
import { isRtl } from '../../../../utils/rtl'; import { isRtl } from '../../../../utils/rtl';
@ -45,7 +45,7 @@ class ReplyIndicator extends ImmutablePureComponent {
<div className='reply-indicator'> <div className='reply-indicator'>
<div className='reply-indicator__header'> <div className='reply-indicator__header'>
<div className='reply-indicator__cancel'> <div className='reply-indicator__cancel'>
<IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted /> <Button title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} inverted />
</div> </div>
<NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'> <NavLink to={`/${status.getIn(['account', 'acct'])}`} className='reply-indicator__display-name'>

View File

@ -0,0 +1,51 @@
import { injectIntl, defineMessages } from 'react-intl'
import { changeComposeSensitivity } from '../../../actions/compose'
import Switch from '../../../components/switch'
const messages = defineMessages({
markAsSensitive: { id: 'compose_form.sensitive.hide', defaultMessage: 'Mark media as sensitive' },
})
const mapStateToProps = state => ({
active: state.getIn(['compose', 'sensitive']),
disabled: state.getIn(['compose', 'spoiler']),
})
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch(changeComposeSensitivity())
},
})
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class SensitiveMediaButton extends PureComponent {
static propTypes = {
active: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
}
render () {
const { active, disabled, onClick, intl } = this.props
return (
<div className={[_s.default, _s.alignItemsStart, _s.px5].join(' ')}>
<Switch
id='mark-sensitive'
type='checkbox'
checked={active}
onChange={onClick}
disabled={disabled}
label={intl.formatMessage(messages.markAsSensitive)}
/>
</div>
)
}
}

View File

@ -0,0 +1,178 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames/bind'
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose'
import { submitCompose } from '../../../actions/compose';
import Button from '../../../components/button'
import Image from '../../../components/image'
import Input from '../../../components/input'
const cx = classNames.bind(_s)
const messages = defineMessages({
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
})
const mapStateToProps = (state, { id, otherProps }) => {
console.log("otherProps:", otherProps)
return {
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
}
}
const mapDispatchToProps = dispatch => ({
onUndo: id => {
dispatch(undoUploadCompose(id));
},
onDescriptionChange: (id, description) => {
dispatch(changeUploadCompose(id, { description }));
},
onSubmit (router) {
dispatch(submitCompose(router));
},
});
export default
@injectIntl
@connect(mapStateToProps, mapDispatchToProps)
class Upload extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
onUndo: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
}
state = {
hovered: false,
focused: false,
dirtyDescription: null,
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit()
}
}
handleSubmit = () => {
this.handleInputBlur()
this.props.onSubmit(this.context.router.history)
}
handleUndoClick = e => {
e.stopPropagation()
this.props.onUndo(this.props.media.get('id'))
}
handleInputChange = e => {
this.setState({ dirtyDescription: e.target.value })
}
handleMouseEnter = () => {
this.setState({ hovered: true })
}
handleMouseLeave = () => {
this.setState({ hovered: false })
}
handleInputFocus = () => {
this.setState({ focused: true })
}
handleClick = () => {
this.setState({ focused: true })
}
handleInputBlur = () => {
const { dirtyDescription } = this.state
this.setState({
focused: false,
dirtyDescription: null,
})
if (dirtyDescription !== null) {
this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription)
}
}
render() {
const { intl, media } = this.props
const active = this.state.hovered || this.state.focused
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''
const descriptionContainerClasses = cx({
default: 1,
positionAbsolute: 1,
right0: 1,
bottom0: 1,
left0: 1,
my5: 1,
ml5: 1,
mr5: 1,
displayNone: !active,
})
console.log("media:", media)
return (
<div
tabIndex='0'
className={[_s.default, _s.width50PC, _s.px5, _s.py5].join(' ')}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClick}
role='button'
>
<div className={[_s.default, _s.radiusSmall, _s.overflowHidden, _s.height158PX].join(' ')}>
<Image
className={[_s.default, _s.height158PX].join(' ')}
src={media.get('preview_url')}
/>
<Button
backgroundColor='black'
color='white'
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='close'
iconWidth='10px'
iconHeight='10px'
iconClassName={_s.inherit}
className={[_s.top0, _s.right0, _s.positionAbsolute, _s.mr5, _s.mt5, _s.px10].join(' ')}
/>
<div className={descriptionContainerClasses}>
<Input
small
hideLabel
id={`input-${media.get('id')}`}
title={intl.formatMessage(messages.description)}
placeholder={intl.formatMessage(messages.description)}
value={description}
maxLength={420}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
/>
</div>
</div>
</div>
)
}
}

View File

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

View File

@ -1,126 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import classNames from 'classnames'
import Button from '../../../../components/button'
import Image from '../../../../components/image'
const messages = defineMessages({
description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' },
delete: { id: 'upload_form.undo', defaultMessage: 'Delete' },
})
export default
@injectIntl
class Upload extends ImmutablePureComponent {
static contextTypes = {
router: PropTypes.object,
}
static propTypes = {
media: ImmutablePropTypes.map.isRequired,
intl: PropTypes.object.isRequired,
onUndo: PropTypes.func.isRequired,
onDescriptionChange: PropTypes.func.isRequired,
onSubmit: PropTypes.func.isRequired,
}
state = {
hovered: false,
focused: false,
dirtyDescription: null,
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit()
}
}
handleSubmit = () => {
this.handleInputBlur()
this.props.onSubmit(this.context.router.history)
}
handleUndoClick = e => {
e.stopPropagation()
this.props.onUndo(this.props.media.get('id'))
}
handleInputChange = e => {
this.setState({ dirtyDescription: e.target.value })
}
handleMouseEnter = () => {
this.setState({ hovered: true })
}
handleMouseLeave = () => {
this.setState({ hovered: false })
}
handleInputFocus = () => {
this.setState({ focused: true })
}
handleClick = () => {
this.setState({ focused: true })
}
handleInputBlur = () => {
const { dirtyDescription } = this.state
this.setState({
focused: false,
dirtyDescription: null,
})
if (dirtyDescription !== null) {
this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription)
}
}
render() {
const { intl, media } = this.props
const active = this.state.hovered || this.state.focused
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''
const focusX = media.getIn(['meta', 'focus', 'x'])
const focusY = media.getIn(['meta', 'focus', 'y'])
const x = ((focusX / 2) + .5) * 100
const y = ((focusY / -2) + .5) * 100
return (
<div className='compose-form-upload' tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'>
<div className='compose-form__upload-thumbnail' style={{ backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}>
<div className={classNames('compose-form__upload__actions', { active })}>
<Button
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='cancel'
/>
</div>
<div className={classNames('compose-form-upload__description', { active })}>
<label>
<span style={{ display: 'none' }}>
{intl.formatMessage(messages.description)}
</span>
<textarea
placeholder={intl.formatMessage(messages.description)}
value={description}
maxLength={420}
onFocus={this.handleInputFocus}
onChange={this.handleInputChange}
onBlur={this.handleInputBlur}
onKeyDown={this.handleKeyDown}
/>
</label>
</div>
</div>
</div>
)
}
}

View File

@ -1,78 +0,0 @@
.compose-form-upload {
flex: 1 1 0;
min-width: 40%;
margin: 5px;
&__actions {
background: linear-gradient(180deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
opacity: 0;
transition: opacity .1s ease;
@include flex(space-between, flex-start);
.icon-button {
flex: 0 1 auto;
color: $gab-secondary-text;
padding: 10px;
font-family: inherit;
@include text-sizing(14px, 500);
&:hover,
&:focus,
&:active {
color: $gab-text-highlight;
}
}
&.active {
opacity: 1;
}
}
&__description {
z-index: 2;
box-sizing: border-box;
background: linear-gradient(0deg, rgba($base-shadow-color, 0.8) 0, rgba($base-shadow-color, 0.35) 80%, transparent);
padding: 10px;
opacity: 0;
transition: opacity .1s ease;
@include abs-position(auto, 0, 0, 0);
textarea {
background: rgba(0, 0, 0, 0.3);
box-sizing: border-box;
background: transparent;
color: $gab-secondary-text;
border: 1px solid $gab-secondary-text;
outline: none;
padding: 10px;
margin: 0;
width: 100%;
font-family: inherit;
@include text-sizing(14px, 500);
&:focus {
color: #fff;
}
&::placeholder {
color: $gab-secondary-text;
}
}
&.active {
opacity: 1;
}
}
&__thumbnail {
border-radius: 4px;
overflow: hidden;
@include size(100%, 140px);
@include background-image("");
}
}

View File

@ -1,11 +1,13 @@
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 UploadProgress from '../upload_progress'; import ProgressBar from '../../../../components/progress_bar'
import UploadContainer from '../../containers/upload_container'; import Upload from '../upload'
import SensitiveButtonContainer from '../../containers/sensitive_button_container'; import SensitiveMediaButton from '../sensitive_media_button'
const mapStateToProps = state => ({ const mapStateToProps = state => ({
mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')), mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')),
isUploading: state.getIn(['compose', 'is_uploading']),
uploadProgress: state.getIn(['compose', 'progress']),
}); });
export default export default
@ -14,24 +16,38 @@ class UploadForm extends ImmutablePureComponent {
static propTypes = { static propTypes = {
mediaIds: ImmutablePropTypes.list.isRequired, mediaIds: ImmutablePropTypes.list.isRequired,
isUploading: PropTypes.bool,
uploadProgress: PropTypes.number,
}; };
render () { render () {
const { mediaIds } = this.props; const {
mediaIds,
isUploading,
uploadProgress
} = this.props
return ( return (
<div className='compose-form-upload-wrapper'> <div className={_s.default}>
<UploadProgress /> <div className={[_s.default, _s.flexRow, _s.flexWrap].join(' ')}>
{
<div className='compose-form-uploads-wrapper'> mediaIds.map(id => (
{mediaIds.map(id => ( <Upload id={id} key={id} />
<UploadContainer id={id} key={id} /> ))
))} }
</div> </div>
{!mediaIds.isEmpty() && <SensitiveButtonContainer />} {
!mediaIds.isEmpty() &&
<SensitiveMediaButton />
}
{
isUploading &&
<ProgressBar small progress={uploadProgress} />
}
</div> </div>
); )
} }
} }

View File

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

View File

@ -1,48 +0,0 @@
import { FormattedMessage } from 'react-intl';
import spring from 'react-motion/lib/spring';
import Motion from '../../../ui/util/optional_motion';
import Icon from '../../../../components/icon';
const mapStateToProps = state => ({
active: state.getIn(['compose', 'is_uploading']),
progress: state.getIn(['compose', 'progress']),
});
export default
@connect(mapStateToProps)
class UploadProgress extends PureComponent {
static propTypes = {
active: PropTypes.bool,
progress: PropTypes.number,
};
render () {
const { active, progress } = this.props;
if (!active) {
return null;
}
return (
<div className='upload-progress'>
<div className='upload-progress__icon'>
<Icon id='upload' />
</div>
<div className='upload-progress__message'>
<FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' />
<div className='upload-progress__backdrop'>
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}>
{({ width }) =>
<div className='upload-progress__tracker' style={{ width: `${width}%` }} />
}
</Motion>
</div>
</div>
</div>
);
}
}

View File

@ -1,39 +0,0 @@
.upload-progress {
padding: 10px;
color: $lighter-text-color;
overflow: hidden;
display: flex;
.fa {
font-size: 34px;
margin-right: 10px;
}
span {
text-transform: uppercase;
display: block;
@include text-sizing(12px, 500);
}
&__message {
flex: 1 1 auto;
}
&__backdrop {
border-radius: 6px;
background: $ui-base-lighter-color;
position: relative;
margin-top: 5px;
@include size(100%, 6px);
}
&__tracker {
height: 6px;
background: $ui-highlight-color;
border-radius: 6px;
@include abs-position(0, auto, auto, 0);
}
}

View File

@ -1,56 +0,0 @@
import classNames from 'classnames';
import { injectIntl, defineMessages, FormattedMessage } from 'react-intl';
import { changeComposeSensitivity } from '../../../actions/compose';
const messages = defineMessages({
marked: { id: 'compose_form.sensitive.marked', defaultMessage: 'Media is marked as sensitive' },
unmarked: { id: 'compose_form.sensitive.unmarked', defaultMessage: 'Media is not marked as sensitive' },
});
const mapStateToProps = state => ({
active: state.getIn(['compose', 'sensitive']),
disabled: state.getIn(['compose', 'spoiler']),
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch(changeComposeSensitivity());
},
});
class SensitiveButton extends PureComponent {
static propTypes = {
active: PropTypes.bool,
disabled: PropTypes.bool,
onClick: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
render () {
const { active, disabled, onClick, intl } = this.props;
return (
<div className='compose-form__sensitive-button'>
<label className={classNames('icon-button', { active })} title={intl.formatMessage(active ? messages.marked : messages.unmarked)}>
<input
name='mark-sensitive'
type='checkbox'
checked={active}
onChange={onClick}
disabled={disabled}
/>
<span className={classNames('checkbox', { active })} />
<FormattedMessage id='compose_form.sensitive.hide' defaultMessage='Mark media as sensitive' />
</label>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton));

View File

@ -1,26 +0,0 @@
import Upload from '../components/upload';
import { undoUploadCompose, changeUploadCompose } from '../../../actions/compose';
import { openModal } from '../../../actions/modal';
import { submitCompose } from '../../../actions/compose';
const mapStateToProps = (state, { id }) => ({
media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id),
});
const mapDispatchToProps = dispatch => ({
onUndo: id => {
dispatch(undoUploadCompose(id));
},
onDescriptionChange: (id, description) => {
dispatch(changeUploadCompose(id, { description }));
},
onSubmit (router) {
dispatch(submitCompose(router));
},
});
export default connect(mapStateToProps, mapDispatchToProps)(Upload);

View File

@ -6,7 +6,6 @@ import { makeGetAccount } from '../../../../selectors';
import Button from '../../../../components/button' import Button from '../../../../components/button'
import Avatar from '../../../../components/avatar'; import Avatar from '../../../../components/avatar';
import DisplayName from '../../../../components/display_name'; import DisplayName from '../../../../components/display_name';
import IconButton from '../../../../components/icon_button';
const messages = defineMessages({ const messages = defineMessages({
authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' }, authorize: { id: 'follow_request.authorize', defaultMessage: 'Authorize' },
@ -64,10 +63,10 @@ class AccountAuthorize extends ImmutablePureComponent {
<div className='account--panel'> <div className='account--panel'>
<div className='account--panel__button'> <div className='account--panel__button'>
<IconButton title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} /> <Button title={intl.formatMessage(messages.authorize)} icon='check' onClick={onAuthorize} />
</div> </div>
<div className='account--panel__button'> <div className='account--panel__button'>
<IconButton title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} /> <Button title={intl.formatMessage(messages.reject)} icon='times' onClick={onReject} />
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@ import { defineMessages, injectIntl } from 'react-intl';
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 { removeFromListAdder, addToListAdder } from '../../../../actions/lists'; import { removeFromListAdder, addToListAdder } from '../../../../actions/lists';
import IconButton from '../../../../components/icon_button'; import Button from '../../../../components/button';
import Icon from '../../../../components/icon'; import Icon from '../../../../components/icon';
const messages = defineMessages({ const messages = defineMessages({
@ -51,9 +51,9 @@ class List extends ImmutablePureComponent {
<div className='list__btn-block'> <div className='list__btn-block'>
{ {
added ? added ?
<IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} /> <Button icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />
: :
<IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} /> <Button icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />
} }
</div> </div>
</div> </div>

View File

@ -5,7 +5,7 @@ import { createSelector } from 'reselect';
import { setupListAdder, resetListAdder } from '../../actions/lists'; import { setupListAdder, resetListAdder } from '../../actions/lists';
import List from './components/list'; import List from './components/list';
import Account from '../../components/account'; import Account from '../../components/account';
import IconButton from '../../components/icon_button'; import Button from '../../components/button';
// import NewListForm from '../lists_directory/components/new_list_form'; // import NewListForm from '../lists_directory/components/new_list_form';
const getOrderedLists = createSelector([state => state.get('lists')], lists => { const getOrderedLists = createSelector([state => state.get('lists')], lists => {
@ -70,7 +70,7 @@ class ListAdder extends ImmutablePureComponent {
<h3 className='compose-modal__header__title'> <h3 className='compose-modal__header__title'>
{intl.formatMessage(messages.headerTitle)} {intl.formatMessage(messages.headerTitle)}
</h3> </h3>
<IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} /> <Button className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
</div> </div>
<div className='compose-modal__content'> <div className='compose-modal__content'>
<div className='list-adder'> <div className='list-adder'>

View File

@ -6,7 +6,7 @@ import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
import { makeGetAccount } from '../../../selectors'; import { makeGetAccount } from '../../../selectors';
import Avatar from '../../../components/avatar'; import Avatar from '../../../components/avatar';
import DisplayName from '../../../components/display_name'; import DisplayName from '../../../components/display_name';
import IconButton from '../../../components/icon_button'; import Button from '../../../components/button';
const messages = defineMessages({ const messages = defineMessages({
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' }, remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
@ -52,9 +52,9 @@ class Account extends ImmutablePureComponent {
let button; let button;
if (added) { if (added) {
button = <IconButton icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />; button = <Button icon='times' title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
} else { } else {
button = <IconButton icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />; button = <Button icon='plus' title={intl.formatMessage(messages.add)} onClick={onAdd} />;
} }
return ( return (

View File

@ -1,22 +1,32 @@
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 { injectIntl, defineMessages } from 'react-intl'; import { injectIntl, defineMessages } from 'react-intl'
import { setupListEditor, resetListEditor } from '../../actions/lists'; import isObject from 'lodash.isobject'
import Account from './components/account'; import { setupListEditor, resetListEditor } from '../../actions/lists'
import ListEditorSearch from './components/list_editor_search'; import Account from './components/account'
import EditListForm from './components/edit_list_form/edit_list_form'; import ListEditorSearch from './components/list_editor_search'
import IconButton from '../../components/icon_button'; import EditListForm from './components/edit_list_form/edit_list_form'
import Button from '../../components/button'
import Input from '../../components/input' import Input from '../../components/input'
const mapStateToProps = state => ({ const mapStateToProps = (state, { params }) => {
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']), console.log("params:", params)
});
const listId = isObject(params) ? params['id'] : null
return {
listId,
title: state.getIn(['listEditor', 'title']),
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
searchAccountIds: state.getIn(['listEditor', 'suggestions', 'items']),
}
}
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
onInitialize: listId => dispatch(setupListEditor(listId)), onInitialize: listId => dispatch(setupListEditor(listId)),
onReset: () => dispatch(resetListEditor()), onReset: () => dispatch(resetListEditor()),
}); })
const messages = defineMessages({ const messages = defineMessages({
close: { id: 'lightbox.close', defaultMessage: 'Close' }, close: { id: 'lightbox.close', defaultMessage: 'Close' },
@ -24,7 +34,8 @@ const messages = defineMessages({
addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' }, addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' },
removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' }, removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
editList: { id: 'lists.edit', defaultMessage: 'Edit list' }, editList: { id: 'lists.edit', defaultMessage: 'Edit list' },
}); editListTitle: { id: 'lists.new.edit_title_placeholder', defaultMessage: 'Edit list title' },
})
export default export default
@connect(mapStateToProps, mapDispatchToProps) @connect(mapStateToProps, mapDispatchToProps)
@ -32,6 +43,7 @@ export default
class ListEdit extends ImmutablePureComponent { class ListEdit extends ImmutablePureComponent {
static propTypes = { static propTypes = {
title: PropTypes.string,
listId: PropTypes.string.isRequired, listId: PropTypes.string.isRequired,
onClose: PropTypes.func.isRequired, onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
@ -39,63 +51,79 @@ class ListEdit extends ImmutablePureComponent {
onReset: PropTypes.func.isRequired, onReset: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list.isRequired, accountIds: ImmutablePropTypes.list.isRequired,
searchAccountIds: ImmutablePropTypes.list.isRequired, searchAccountIds: ImmutablePropTypes.list.isRequired,
}; }
componentDidMount() { componentDidMount() {
const { onInitialize, listId } = this.props; const { onInitialize, listId } = this.props
if (listId) onInitialize(listId); console.log("listId:", listId)
if (listId) {
onInitialize(listId)
}
} }
componentWillUnmount() { componentWillUnmount() {
this.props.onReset(); this.props.onReset()
} }
onClickClose = () => { onClickClose = () => {
this.props.onClose('LIST_ADDER'); this.props.onClose('LIST_ADDER')
}; }
render() { render() {
const { accountIds, searchAccountIds, intl } = this.props; const {
title,
accountIds,
searchAccountIds,
intl
} = this.props
console.log("title:", title)
return ( return (
<div> <div>
<Input <Input
title={intl.formatMessage(messages.editList)} title={intl.formatMessage(messages.editListTitle)}
placeholder='My new list title...'
value={title}
// onChange={onChange}
// onSubmit={onSubmit}
// disabled={disabled}
/> />
{
/*
<div className='compose-modal__header'>
<h3 className='compose-modal__header__title'>
{intl.formatMessage(messages.editList)}
</h3>
<Button className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
</div>
<div className='compose-modal__content'>
<div className='list-editor'>
<EditListForm />
<br />
{
accountIds.size > 0 &&
<div>
<div className='list-editor__accounts'>
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
</div>
</div>
}
<br />
<ListEditorSearch />
<div className='list-editor__accounts'>
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
</div>
</div>
</div>
*/ }
</div> </div>
) )
// return (
// <div className='modal-root__modal compose-modal'>
// <div className='compose-modal__header'>
// <h3 className='compose-modal__header__title'>
// {intl.formatMessage(messages.editList)}
// </h3>
// <IconButton className='compose-modal__close' title={intl.formatMessage(messages.close)} icon='times' onClick={this.onClickClose} size={20} />
// </div>
// <div className='compose-modal__content'>
// <div className='list-editor'>
// <EditListForm />
// <br />
// {
// accountIds.size > 0 &&
// <div>
// <div className='list-editor__accounts'>
// {accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
// </div>
// </div>
// }
// <br />
// <ListEditorSearch />
// <div className='list-editor__accounts'>
// {searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
// </div>
// </div>
// </div>
// </div>
// );
} }
} }

View File

@ -60,7 +60,7 @@ class ListsDirectory extends ImmutablePureComponent {
const emptyMessage = intl.formatMessage(messages.empty) const emptyMessage = intl.formatMessage(messages.empty)
const listItems = lists.map(list => ({ const listItems = lists.map(list => ({
to: `/list/${list.get('id')}`, to: `/lists/${list.get('id')}`,
title: list.get('title'), title: list.get('title'),
})) }))

View File

@ -1,89 +0,0 @@
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { FormattedMessage } from 'react-intl';
import ColumnHeaderSettingButton from '../../../../components/column_header_setting_button';
import SettingSwitch from '../../../../components/setting_switch';
export default class ColumnSettings extends ImmutablePureComponent {
static propTypes = {
settings: ImmutablePropTypes.map.isRequired,
pushSettings: ImmutablePropTypes.map.isRequired,
onChange: PropTypes.func.isRequired,
onClear: PropTypes.func.isRequired,
};
onPushChange = (path, checked) => {
this.props.onChange(['push', ...path], checked);
}
render() {
const { settings, pushSettings, onChange, onClear } = this.props;
const filterShowStr = <FormattedMessage id='notifications.column_settings.filter_bar.show' defaultMessage='Show' />;
const filterAdvancedStr = <FormattedMessage id='notifications.column_settings.filter_bar.advanced' defaultMessage='Display all categories' />;
const alertStr = <FormattedMessage id='notifications.column_settings.alert' defaultMessage='Desktop notifications' />;
const showStr = <FormattedMessage id='notifications.column_settings.show' defaultMessage='Show in column' />;
const soundStr = <FormattedMessage id='notifications.column_settings.sound' defaultMessage='Play sound' />;
const showPushSettings = pushSettings.get('browserSupport') && pushSettings.get('isSubscribed');
const pushStr = showPushSettings && <FormattedMessage id='notifications.column_settings.push' defaultMessage='Push notifications' />;
return (
<div>
<ColumnHeaderSettingButton
onClick={onClear}
title={<FormattedMessage id='notifications.clear' defaultMessage='Clear notifications' />}
icon='eraser'
/>
<div role='group' aria-labelledby='notifications-filter-bar'>
<FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
<SettingSwitch id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'show']} onChange={onChange} label={filterShowStr} />
<SettingSwitch id='show-filter-bar' prefix='notifications' settings={settings} settingPath={['quickFilter', 'advanced']} onChange={onChange} label={filterAdvancedStr} />
</div>
<div role='group' aria-labelledby='notifications-follow'>
<FormattedMessage id='notifications.column_settings.follow' defaultMessage='New followers:' />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'follow']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'follow']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'follow']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'follow']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-favorite'>
<FormattedMessage id='notifications.column_settings.favorite' defaultMessage='Favorites:' />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'favorite']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'favorite']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'favorite']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'favorite']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-mention'>
<FormattedMessage id='notifications.column_settings.mention' defaultMessage='Mentions:' />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'mention']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'mention']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'mention']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'mention']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-repost'>
<FormattedMessage id='notifications.column_settings.repost' defaultMessage='Reposts:' />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'repost']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'repost']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'repost']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'repost']} onChange={onChange} label={soundStr} />
</div>
<div role='group' aria-labelledby='notifications-poll'>
<FormattedMessage id='notifications.column_settings.poll' defaultMessage='Poll results:' />
<SettingSwitch prefix='notifications_desktop' settings={settings} settingPath={['alerts', 'poll']} onChange={onChange} label={alertStr} />
{showPushSettings && <SettingSwitch prefix='notifications_push' settings={pushSettings} settingPath={['alerts', 'poll']} onChange={this.onPushChange} label={pushStr} />}
<SettingSwitch prefix='notifications' settings={settings} settingPath={['shows', 'poll']} onChange={onChange} label={showStr} />
<SettingSwitch prefix='notifications' settings={settings} settingPath={['sounds', 'poll']} onChange={onChange} label={soundStr} />
</div>
</div>
);
}
}

View File

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

View File

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

View File

@ -1,95 +0,0 @@
import { defineMessages, injectIntl } from 'react-intl';
const messages = defineMessages({
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
favorites: { id: 'notifications.filter.favorites', defaultMessage: 'Favorites' },
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
filterAll: { id: 'notifications.filter.all', defaultMessage: 'All' },
filterMentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
});
export default
@injectIntl
class NotificationFilterBar extends PureComponent {
static propTypes = {
selectFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired,
advancedMode: PropTypes.bool.isRequired,
intl: PropTypes.object.isRequired,
};
onClick (notificationType) {
return () => this.props.selectFilter(notificationType);
}
render () {
const { selectedFilter, advancedMode, intl } = this.props;
if (!advancedMode) {
return (
{ /* <SectionHeadlineBar
items={[
{
className: selectedFilter === 'all' ? 'active' : '',
onClick: this.onClick('all'),
title: intl.formatMessage(messages.filterAll),
},
{
className: selectedFilter === 'mention' ? 'active' : '',
onClick: this.onClick('mention'),
title: intl.formatMessage(messages.filterMentions),
}
]}
/> */ }
)
}
return (
<div />
)
/* <SectionHeadlineBar
items={[
{
className: selectedFilter === 'all' ? 'active' : '',
onClick: this.onClick('all'),
title: intl.formatMessage(messages.filterAll),
},
{
className: selectedFilter === 'mention' ? 'active' : '',
onClick: this.onClick('mention'),
title: intl.formatMessage(messages.mentions),
icon: 'at',
},
{
className: selectedFilter === 'favorite' ? 'active' : '',
onClick: this.onClick('favorite'),
title: intl.formatMessage(messages.favorites),
icon: 'star',
},
{
className: selectedFilter === 'reblog' ? 'active' : '',
onClick: this.onClick('reblog'),
title: intl.formatMessage(messages.boosts),
icon: 'retweet',
},
{
className: selectedFilter === 'poll' ? 'active' : '',
onClick: this.onClick('poll'),
title: intl.formatMessage(messages.polls),
icon: 'tasks',
},
{
className: selectedFilter === 'follow' ? 'active' : '',
onClick: this.onClick('follow'),
title: intl.formatMessage(messages.follows),
icon: 'user-plus',
},
]}
/> */
}
}

View File

@ -16,6 +16,7 @@ const mapStateToProps = state => ({
pushSettings: state.get('push_notifications'), pushSettings: state.get('push_notifications'),
}); });
// : todo : put all notification settings actually IN settings
const mapDispatchToProps = (dispatch, { intl }) => ({ const mapDispatchToProps = (dispatch, { intl }) => ({
onChange (path, checked) { onChange (path, checked) {

View File

@ -1,15 +0,0 @@
import { setFilter } from '../../../actions/notifications';
import FilterBar from '../components/notification_filter_bar';
const makeMapStateToProps = state => ({
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
advancedMode: state.getIn(['settings', 'notifications', 'quickFilter', 'advanced']),
});
const mapDispatchToProps = (dispatch) => ({
selectFilter (newActiveFilter) {
dispatch(setFilter(newActiveFilter));
},
});
export default connect(makeMapStateToProps, mapDispatchToProps)(FilterBar);

View File

@ -165,7 +165,7 @@ class SwitchingArea extends PureComponent {
<WrappedRoute path='/lists' exact page={ListsPage} component={ListsDirectory} content={children} /> <WrappedRoute path='/lists' exact page={ListsPage} component={ListsDirectory} content={children} />
<WrappedRoute path='/lists/create' exact page={ModalPage} component={ListCreate} content={children} componentParams={{ title: 'Create List' }} /> <WrappedRoute path='/lists/create' exact page={ModalPage} component={ListCreate} content={children} componentParams={{ title: 'Create List' }} />
<WrappedRoute path='/lists/:id/edit' exact page={ModalPage} component={ListEdit} content={children} componentParams={{ title: 'Edit List' }} /> <WrappedRoute path='/lists/:id/edit' exact page={ModalPage} component={ListEdit} content={children} componentParams={{ title: 'Edit List' }} />
<WrappedRoute path='/list/:id' page={ListPage} component={ListTimeline} content={children} /> <WrappedRoute path='/lists/:id' page={ListPage} component={ListTimeline} content={children} />
<WrappedRoute path='/notifications' exact page={NotificationsPage} component={Notifications} content={children} /> <WrappedRoute path='/notifications' exact page={NotificationsPage} component={Notifications} content={children} />

View File

@ -5,6 +5,7 @@ import ColumnHeader from '../components/column_header'
import Sidebar from '../components/sidebar' import Sidebar from '../components/sidebar'
// import Header from '../components/header' // import Header from '../components/header'
// import Footer from '../components/footer' // import Footer from '../components/footer'
import FloatingActionButton from '../components/floating_action_button'
import Responsive from '../features/ui/util/responsive_component' import Responsive from '../features/ui/util/responsive_component'
export default class DefaultLayout extends PureComponent { export default class DefaultLayout extends PureComponent {
@ -19,9 +20,6 @@ export default class DefaultLayout extends PureComponent {
render() { render() {
const { children, title, showBackBtn, layout, actions, tabs } = this.props const { children, title, showBackBtn, layout, actions, tabs } = this.props
// const shouldHideFAB = path => path.match(/^\/posts\/|^\/search|^\/getting-started/);
// const floatingActionButton = shouldHideFAB(this.context.router.history.location.pathname) ? null : <button key='floating-action-button' onClick={this.handleOpenComposeModal} className='floating-action-button' aria-label={intl.formatMessage(messages.publish)}></button>;
console.log("Constants.BREAKPOINT_EXTRA_SMALL:", Constants.BREAKPOINT_EXTRA_SMALL) console.log("Constants.BREAKPOINT_EXTRA_SMALL:", Constants.BREAKPOINT_EXTRA_SMALL)
return ( return (
@ -43,9 +41,11 @@ export default class DefaultLayout extends PureComponent {
tabs={tabs} tabs={tabs}
/> />
</div> </div>
<div className={[_s.default, _s.width340PX].join(' ')}> <Responsive min={Constants.BREAKPOINT_EXTRA_SMALL}>
<Search /> <div className={[_s.default, _s.width340PX].join(' ')}>
</div> <Search />
</div>
</Responsive>
</div> </div>
</div> </div>
@ -68,6 +68,10 @@ export default class DefaultLayout extends PureComponent {
</div> </div>
</Responsive> </Responsive>
<Responsive max={Constants.BREAKPOINT_EXTRA_SMALL}>
<FloatingActionButton />
</Responsive>
</div> </div>
</main> </main>

View File

@ -24,14 +24,6 @@ class GroupsPage extends PureComponent {
document.title = 'Groups - Gab' document.title = 'Groups - Gab'
} }
handleClickNewList () {
console.log("handleClickNewList")
}
handleClickEditLists () {
console.log("handleClickEditLists")
}
render() { render() {
const { children, onOpenGroupCreateModal } = this.props const { children, onOpenGroupCreateModal } = this.props

View File

@ -14,7 +14,7 @@ const mapStateToProps = (state, props) => ({
const mapDispatchToProps = (dispatch, { list }) => ({ const mapDispatchToProps = (dispatch, { list }) => ({
onOpenListEditModal() { onOpenListEditModal() {
dispatch(openModal('GROUP_DELETE', { dispatch(openModal('LIST_EDIT', {
list, list,
})) }))
}, },

View File

@ -1,19 +1,87 @@
import { Fragment } from 'react' import { Fragment } from 'react'
import { defineMessages, injectIntl } from 'react-intl';
import { setFilter } from '../actions/notifications'
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 NotificationFilterPanel from '../components/panel/notification_filter_panel' import NotificationFilterPanel from '../components/panel/notification_filter_panel'
import TrendsPanel from '../components/panel/trends_panel' import TrendsPanel from '../components/panel/trends_panel'
import DefaultLayout from '../layouts/default_layout' import DefaultLayout from '../layouts/default_layout'
export default class NotificationsPage extends PureComponent { const messages = defineMessages({
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
favorites: { id: 'notifications.filter.favorites', defaultMessage: 'Favorites' },
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
filterAll: { id: 'notifications.filter.all', defaultMessage: 'All' },
filterMentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
});
const makeMapStateToProps = state => ({
selectedFilter: state.getIn(['settings', 'notifications', 'quickFilter', 'active']),
});
const mapDispatchToProps = (dispatch) => ({
selectFilter (newActiveFilter) {
dispatch(setFilter(newActiveFilter));
},
});
export default
@injectIntl
@connect(makeMapStateToProps, mapDispatchToProps)
class NotificationsPage extends PureComponent {
static propTypes = {
selectFilter: PropTypes.func.isRequired,
selectedFilter: PropTypes.string.isRequired,
intl: PropTypes.object.isRequired,
}
componentDidMount() { componentDidMount() {
document.title = 'Notifications - Gab' document.title = 'Notifications - Gab'
} }
onClick (notificationType) {
return () => this.props.selectFilter(notificationType);
}
render() { render() {
const { children } = this.props const { children } = this.props
const tabs = [
{
title: 'All',
onClick: null,
active: false,
},
{
title: ' @ ',
onClick: null,
active: false,
},
{
icon: 'like',
onClick: null,
active: false,
},
{
icon: 'repost',
onClick: null,
active: false,
},
{
icon: 'poll',
onClick: null,
active: false,
},
{
icon: 'user-plus',
onClick: null,
active: false,
},
]
return ( return (
<DefaultLayout <DefaultLayout
title='Notifications' title='Notifications'
@ -25,7 +93,7 @@ export default class NotificationsPage extends PureComponent {
<LinkFooter /> <LinkFooter />
</Fragment> </Fragment>
)} )}
showBackBtn tabs={tabs}
> >
{children} {children}
</DefaultLayout> </DefaultLayout>

View File

@ -44,34 +44,9 @@ const initialState = ImmutableMap({
}), }),
notifications: ImmutableMap({ notifications: ImmutableMap({
alerts: ImmutableMap({ // : todo : put all notification settings actually IN settings
follow: true,
favorite: true,
repost: true,
mention: true,
poll: true,
}),
quickFilter: ImmutableMap({ quickFilter: ImmutableMap({
active: 'all', active: 'all',
show: true,
advanced: false,
}),
shows: ImmutableMap({
follow: true,
favorite: true,
repost: true,
mention: true,
poll: true,
}),
sounds: ImmutableMap({
follow: true,
favorite: true,
repost: true,
mention: true,
poll: true,
}), }),
}), }),

View File

@ -89,6 +89,10 @@ body {
white-space: inherit; white-space: inherit;
} }
.inheritFill {
fill: inherit;
}
.flexNormal { .flexNormal {
flex-basis: 0%; flex-basis: 0%;
flex-grow: 1; flex-grow: 1;
@ -248,6 +252,10 @@ body {
cursor: pointer cursor: pointer
} }
.cursorNotAllowed {
cursor: not-allowed;
}
.pointerEventsAuto>* { .pointerEventsAuto>* {
pointer-events: auto; pointer-events: auto;
} }
@ -300,6 +308,14 @@ body {
background-color: rgba(54, 233, 145, 0.1); background-color: rgba(54, 233, 145, 0.1);
} }
.backgroundColorBlack {
background-color: #3B3B3B;
}
.backgroundColorBlackOpaque_onHover:hover {
background-color: rgba(59, 59, 59, 0.8);
}
.backgroundColorOpaque { .backgroundColorOpaque {
background-color: rgba(0, 0, 0, 0.4); background-color: rgba(0, 0, 0, 0.4);
} }
@ -324,6 +340,10 @@ body {
background-color: #38A16B; background-color: #38A16B;
} }
.backgroundColorDanger {
background-color: #DE2960;
}
.colorPrimary { .colorPrimary {
color: #000; color: #000;
} }
@ -356,6 +376,10 @@ body {
fill: #fff; fill: #fff;
} }
.fillColorWhite_onHover:hover {
fill: #fff;
}
.fillColorBrand { .fillColorBrand {
fill: #21cf7a; fill: #21cf7a;
} }
@ -761,6 +785,10 @@ body {
margin-bottom: -5px; margin-bottom: -5px;
} }
.mt2 {
margin-top: 2px;
}
.mr2 { .mr2 {
margin-right: 2px; margin-right: 2px;
} }
@ -909,6 +937,10 @@ body {
opacity: 0; opacity: 0;
} }
.opacity05 {
opacity: 0.5;
}
.opacity1 { .opacity1 {
opacity: 1; opacity: 1;
} }