Progress
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
import { Fragment } from 'react'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import classNames from 'classnames/bind'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import { isRtl } from '../../utils/rtl';
|
||||
import { textAtCursorMatchesToken } from '../../utils/cursor_token_match';
|
||||
import AutosuggestAccount from '../autosuggest_account';
|
||||
import AutosuggestEmoji from '../autosuggest_emoji';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import Textarea from 'react-textarea-autosize'
|
||||
import { isRtl } from '../../utils/rtl'
|
||||
import { textAtCursorMatchesToken } from '../../utils/cursor_token_match'
|
||||
import AutosuggestAccount from '../autosuggest_account'
|
||||
import AutosuggestEmoji from '../autosuggest_emoji'
|
||||
import Input from '../input'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
@@ -191,12 +192,24 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { value, small, suggestions, disabled, placeholder, onKeyUp, autoFocus, children, className, id, maxLength, textarea } = this.props;
|
||||
const { suggestionsHidden } = this.state;
|
||||
const style = { direction: 'ltr' };
|
||||
const {
|
||||
value,
|
||||
small,
|
||||
suggestions,
|
||||
disabled,
|
||||
placeholder,
|
||||
onKeyUp,
|
||||
autoFocus,
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
maxLength,
|
||||
textarea
|
||||
} = this.props
|
||||
|
||||
if (isRtl(value)) {
|
||||
style.direction = 'rtl';
|
||||
const { suggestionsHidden } = this.state
|
||||
const style = {
|
||||
direction: isRtl(value) ? 'rtl' : 'ltr',
|
||||
}
|
||||
|
||||
const textClasses = cx({
|
||||
@@ -217,7 +230,7 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
|
||||
if (textarea) {
|
||||
return (
|
||||
<Fragment>
|
||||
<Fragment>
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<div className={[_s.default, _s.marginLeft5PX].join(' ')}>
|
||||
<Textarea
|
||||
@@ -249,11 +262,11 @@ export default class AutosuggestTextbox extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='autosuggest-input'>
|
||||
<label>
|
||||
<div className={[_s.default, _s.flexGrow1].join(' ')}>
|
||||
<label className={[_s.default].join(' ')}>
|
||||
<span style={{ display: 'none' }}>{placeholder}</span>
|
||||
|
||||
<input
|
||||
<Input
|
||||
type='text'
|
||||
ref={this.setTextbox}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -79,11 +79,14 @@ export default class Button extends PureComponent {
|
||||
...otherProps
|
||||
} = this.props
|
||||
|
||||
const theIcon = !!icon ? <Icon id={icon} width={iconWidth} height={iconWidth} className={iconClassName} /> : undefined
|
||||
|
||||
if (backgroundColor === 'tertiary') {
|
||||
console.log("className:", className)
|
||||
}
|
||||
const theIcon = !!icon ? (
|
||||
<Icon
|
||||
id={icon}
|
||||
width={iconWidth}
|
||||
height={iconWidth}
|
||||
className={iconClassName}
|
||||
/>
|
||||
) : undefined
|
||||
|
||||
// : todo :
|
||||
const classes = cx(className, {
|
||||
@@ -93,6 +96,7 @@ export default class Button extends PureComponent {
|
||||
cursorPointer: 1,
|
||||
textAlignCenter: 1,
|
||||
outlineNone: 1,
|
||||
flexRow: !!children && !!icon,
|
||||
|
||||
backgroundColorPrimary: backgroundColor === COLORS.white,
|
||||
backgroundColorBrand: backgroundColor === COLORS.brand,
|
||||
@@ -143,6 +147,7 @@ export default class Button extends PureComponent {
|
||||
to: to || undefined,
|
||||
href: href || undefined,
|
||||
onClick: this.handleClick || undefined,
|
||||
...otherProps,
|
||||
}
|
||||
|
||||
if (tagName === 'NavLink' && !!to) {
|
||||
|
||||
@@ -1,67 +1,53 @@
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import TabBar from './tab_bar'
|
||||
import Icon from './icon'
|
||||
import Button from './button'
|
||||
import Heading from './heading'
|
||||
|
||||
const messages = defineMessages({
|
||||
show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
|
||||
})
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class ColumnHeader extends PureComponent {
|
||||
export default class ColumnHeader extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
title: PropTypes.node,
|
||||
icon: PropTypes.string,
|
||||
active: PropTypes.bool,
|
||||
children: PropTypes.node,
|
||||
showBackBtn: PropTypes.bool,
|
||||
actions: PropTypes.array,
|
||||
tabs: PropTypes.array,
|
||||
}
|
||||
|
||||
state = {
|
||||
collapsed: true,
|
||||
}
|
||||
|
||||
historyBack = () => {
|
||||
if (window.history && window.history.length === 1) {
|
||||
this.context.router.history.push('/home') // homehack
|
||||
this.context.router.history.push('/home')
|
||||
} else {
|
||||
this.context.router.history.goBack()
|
||||
}
|
||||
}
|
||||
|
||||
handleToggleClick = (e) => {
|
||||
e.stopPropagation()
|
||||
this.setState({
|
||||
collapsed: !this.state.collapsed,
|
||||
})
|
||||
}
|
||||
|
||||
handleBackClick = () => {
|
||||
this.historyBack()
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, showBackBtn, tabs, icon, active, children, actions, intl: { formatMessage } } = this.props
|
||||
const { collapsed } = this.state
|
||||
const {
|
||||
title,
|
||||
showBackBtn,
|
||||
tabs,
|
||||
actions
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.height100PC, _s.flexRow].join(' ')}>
|
||||
{
|
||||
showBackBtn &&
|
||||
<button className={[_s.default, _s.cursorPointer, _s.backgroundTransparent, _s.alignItemsCenter, _s.marginRight10PX, _s.justifyContentCenter].join(' ')}>
|
||||
<Icon className={[_s.marginRight5PX, _s.fillColorPrimary].join(' ')} id='back' width='20px' height='20px' />
|
||||
</button>
|
||||
<Button
|
||||
backgroundColor='none'
|
||||
className={[_s.alignItemsCenter, _s.paddingLeft0, _s.justifyContentCenter].join(' ')}
|
||||
icon='back'
|
||||
iconWidth='20px'
|
||||
iconHeight='20px'
|
||||
iconClassName={[_s.marginRight5PX, _s.fillColorPrimary].join(' ')}
|
||||
onClick={this.handleBackClick}
|
||||
/>
|
||||
}
|
||||
|
||||
<div className={[_s.default, _s.height100PC, _s.justifyContentCenter, _s.marginRight10PX].join(' ')}>
|
||||
@@ -80,13 +66,17 @@ class ColumnHeader extends PureComponent {
|
||||
<div className={[_s.default, _s.backgroundTransparent, _s.flexRow, _s.alignItemsCenter, _s.justifyContentCenter, _s.marginLeftAuto].join(' ')}>
|
||||
{
|
||||
actions.map((action, i) => (
|
||||
<button
|
||||
<Button
|
||||
radiusSmall
|
||||
backgroundColor='tertiary'
|
||||
onClick={() => action.onClick()}
|
||||
key={`column-header-action-btn-${i}`}
|
||||
className={[_s.default, _s.marginLeft5PX, _s.cursorPointer, _s.backgroundSubtle2, _s.paddingHorizontal10PX, _s.paddingVertical10PX, _s.radiusSmall].join(' ')}
|
||||
>
|
||||
<Icon className={_s.fillColorSecondary} id={action.icon} width='20px' height='20px' />
|
||||
</button>
|
||||
className={[_s.marginLeft5PX, _s.paddingHorizontal10PX].join(' ')}
|
||||
iconClassName={_s.fillColorSecondary}
|
||||
icon={action.icon}
|
||||
iconWidth='20px'
|
||||
iconHeight='20px'
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -45,6 +45,8 @@ export default class Icon extends PureComponent {
|
||||
return <I.GlobeIcon {...options} />
|
||||
case 'group':
|
||||
return <I.GroupIcon {...options} />
|
||||
case 'happy':
|
||||
return <I.HappyIcon {...options} />
|
||||
case 'home':
|
||||
return <I.HomeIcon {...options} />
|
||||
case 'like':
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Fragment } from 'react'
|
||||
import classNames from 'classnames/bind'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
@@ -16,6 +17,7 @@ export default class Input extends PureComponent {
|
||||
onBlur: PropTypes.func,
|
||||
onClear: PropTypes.func,
|
||||
title: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -29,7 +31,8 @@ export default class Input extends PureComponent {
|
||||
onFocus,
|
||||
onBlur,
|
||||
onClear,
|
||||
title
|
||||
title,
|
||||
small
|
||||
} = this.props
|
||||
|
||||
const inputClasses = cx({
|
||||
@@ -48,7 +51,7 @@ export default class Input extends PureComponent {
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Fragment>
|
||||
{
|
||||
!!title &&
|
||||
<div className={[_s.default, _s.marginBottom10PX, _s.paddingLeft15PX].join(' ')}>
|
||||
@@ -81,7 +84,7 @@ export default class Input extends PureComponent {
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -3,14 +3,18 @@ import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { decode } from 'blurhash'
|
||||
import { autoPlayGif, displayMedia } from '../initial_state'
|
||||
import classNames from 'classnames/bind'
|
||||
import Icon from './icon'
|
||||
import Image from './image'
|
||||
import Text from './text'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
export default class MediaItem extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
attachment: ImmutablePropTypes.map.isRequired,
|
||||
small: PropTypes.bool
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -55,7 +59,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { attachment } = this.props
|
||||
const { attachment, small } = this.props
|
||||
const { visible, loaded } = this.state
|
||||
|
||||
const status = attachment.get('status')
|
||||
@@ -71,13 +75,33 @@ export default class MediaItem extends ImmutablePureComponent {
|
||||
badge = 'GIF'
|
||||
}
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
positionAbsolute: 1,
|
||||
top0: 1,
|
||||
height100PC: 1,
|
||||
width100PC: 1,
|
||||
paddingVertical5PX: !small,
|
||||
paddingHorizontal5PX: !small,
|
||||
})
|
||||
|
||||
const linkClasses = cx({
|
||||
default: 1,
|
||||
width100PC: 1,
|
||||
height100PC: 1,
|
||||
overflowHidden: 1,
|
||||
border1PX: 1,
|
||||
borderColorSecondary: !small,
|
||||
borderColorWhite: small,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.width25PC, _s.paddingTop25PC].join(' ')}>
|
||||
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.height100PC, _s.width100PC, _s.paddingVertical5PX, _s.paddingHorizontal5PX].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<NavLink
|
||||
to={status.get('url')} /* : todo : */
|
||||
title={title}
|
||||
className={[_s.default, _s.width100PC, _s.height100PC, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}
|
||||
className={linkClasses}
|
||||
>
|
||||
{
|
||||
(!loaded || !visible) &&
|
||||
|
||||
@@ -29,7 +29,7 @@ class ComposeModal extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
onClickClose = () => {
|
||||
const {composeText, dispatch, onClose, intl} = this.props;
|
||||
const { composeText, dispatch, onClose, intl } = this.props;
|
||||
|
||||
if (composeText) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
@@ -44,12 +44,15 @@ class ComposeModal extends ImmutablePureComponent {
|
||||
}
|
||||
};
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const { intl } = this.props;
|
||||
|
||||
return (
|
||||
<ModalLayout title={intl.formatMessage(messages.title)} onClose={this.onClickClose}>
|
||||
<TimelineComposeBlock />
|
||||
<ModalLayout
|
||||
noPadding
|
||||
title={intl.formatMessage(messages.title)} onClose={this.onClickClose}
|
||||
>
|
||||
<TimelineComposeBlock modal />
|
||||
</ModalLayout>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ class ModalBase extends PureComponent {
|
||||
<Fragment>
|
||||
<div
|
||||
role='presentation'
|
||||
className={[_s.default, _s.backgroundColorPrimaryOpaque, _s.positionFixed, _s.z3, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
|
||||
className={[_s.default, _s.backgroundColorOpaque, _s.positionFixed, _s.z3, _s.top0, _s.right0, _s.bottom0, _s.left0].join(' ')}
|
||||
onClick={this.handleOnClose}
|
||||
/>
|
||||
<div
|
||||
|
||||
@@ -17,7 +17,12 @@ class ModalLayout extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, children, intl, onClose } = this.props
|
||||
const {
|
||||
title,
|
||||
children,
|
||||
intl,
|
||||
onClose,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={[_s.width645PX].join(' ')}>
|
||||
@@ -27,12 +32,13 @@ class ModalLayout extends PureComponent {
|
||||
{title}
|
||||
</Heading>
|
||||
<Button
|
||||
className=''
|
||||
backgroundColor='none'
|
||||
title={intl.formatMessage(messages.close)}
|
||||
className={_s.marginLeftAuto}
|
||||
onClick={onClose}
|
||||
icon='times'
|
||||
iconWidth='20px'
|
||||
iconWidth='20px'
|
||||
icon='close'
|
||||
iconWidth='10px'
|
||||
iconWidth='10px'
|
||||
/>
|
||||
</div>
|
||||
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
|
||||
|
||||
@@ -1,52 +1,82 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import AccountContainer from '../../containers/account_container'
|
||||
import { expandAccountMediaTimeline } from '../../actions/timelines'
|
||||
import { getAccountGallery } from '../../selectors'
|
||||
import PanelLayout from './panel_layout'
|
||||
import MediaItem from '../media_item'
|
||||
|
||||
const messages = defineMessages({
|
||||
title: { id: 'media_gallery_panel.title', defaultMessage: 'Media' },
|
||||
show_all: { id: 'media_gallery_panel.all', defaultMessage: 'Show all' },
|
||||
})
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
suggestions: state.getIn(['suggestions', 'items']),
|
||||
})
|
||||
const mapStateToProps = (state, { account }) => {
|
||||
const accountId = !!account ? account.get('id') : -1
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions()),
|
||||
dismissSuggestion: account => dispatch(dismissSuggestion(account.get('id'))),
|
||||
accountId,
|
||||
attachments: getAccountGallery(state, accountId),
|
||||
}
|
||||
}
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@connect(mapStateToProps)
|
||||
@injectIntl
|
||||
class MediaGalleryPanel extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
accountId: PropTypes.string,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
attachments: ImmutablePropTypes.list.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// this.props.fetchSuggestions()
|
||||
const { accountId } = this.props
|
||||
|
||||
if (accountId) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(accountId, {limit: 8}))
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (nextProps.accountId && nextProps.accountId !== this.props.accountId) {
|
||||
this.props.dispatch(expandAccountMediaTimeline(nextProps.accountId, {limit: 8}))
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, account } = this.props
|
||||
const {
|
||||
intl,
|
||||
account,
|
||||
attachments
|
||||
} = this.props
|
||||
|
||||
console.log("account:", account)
|
||||
console.log("account, attachments:", account, attachments)
|
||||
|
||||
if (!account || !attachments) return null
|
||||
if (attachments.size === 0) return null
|
||||
|
||||
return (
|
||||
<PanelLayout
|
||||
noPadding
|
||||
title={intl.formatMessage(messages.title)}
|
||||
headerButtonTitle={intl.formatMessage(messages.show_all)}
|
||||
headerButtonTo='/explore'
|
||||
headerButtonTo={`/${account.get('acct')}/media`}
|
||||
>
|
||||
|
||||
<div className={[_s.default, _s.flexRow, _s.flexWrap, _s.paddingHorizontal10PX, _s.paddingVertical10PX].join(' ')}>
|
||||
{
|
||||
attachments.slice(0, 16).map((attachment) => (
|
||||
<MediaItem
|
||||
small
|
||||
key={attachment.get('id')}
|
||||
attachment={attachment}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PanelLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,6 @@ const mapStateToProps = (state, { account }) => {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions()),
|
||||
@@ -119,7 +118,7 @@ class ProfileInfoPanel extends ImmutablePureComponent {
|
||||
<Divider small />
|
||||
<dl className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')} key={`profile-field-${i}`}>
|
||||
<dt
|
||||
className={[_s.text, _s.dangerousContent].join('')}
|
||||
className={[_s.text, _s.dangerousContent].join(' ')}
|
||||
dangerouslySetInnerHTML={{ __html: pair.get('name_emojified') }}
|
||||
title={pair.get('name')}
|
||||
/>
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
||||
import classNames from 'classnames';
|
||||
import escapeTextContentForBrowser from 'escape-html';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import Motion from '../../features/ui/util/optional_motion';
|
||||
import { vote, fetchPoll } from '../../actions/polls';
|
||||
import emojify from '../emoji/emoji';
|
||||
import RelativeTimestamp from '../relative_timestamp';
|
||||
import Button from '../button';
|
||||
import { Fragment } from 'react'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'
|
||||
import classNames from 'classnames/bind'
|
||||
import escapeTextContentForBrowser from 'escape-html'
|
||||
import spring from 'react-motion/lib/spring'
|
||||
import Motion from '../../features/ui/util/optional_motion'
|
||||
import { vote } from '../../actions/polls'
|
||||
import emojify from '../emoji/emoji'
|
||||
import RelativeTimestamp from '../relative_timestamp'
|
||||
import Button from '../button'
|
||||
import DotTextSeperator from '../dot_text_seperator'
|
||||
import Text from '../text'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const mapStateToProps = (state, { pollId }) => ({
|
||||
poll: state.getIn(['polls', pollId]),
|
||||
});
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
closed: { id: 'poll.closed', defaultMessage: 'Closed' },
|
||||
vote: { id: 'poll.vote', defaultMessage: 'Vote' },
|
||||
refresh: { id: 'poll.refresh', defaultMessage: 'Refresh' },
|
||||
});
|
||||
})
|
||||
|
||||
const makeEmojiMap = record => record.get('emojis').reduce((obj, emoji) => {
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS();
|
||||
return obj;
|
||||
}, {});
|
||||
obj[`:${emoji.get('shortcode')}:`] = emoji.toJS()
|
||||
return obj
|
||||
}, {})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps)
|
||||
@@ -35,71 +40,91 @@ class Poll extends ImmutablePureComponent {
|
||||
intl: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func,
|
||||
disabled: PropTypes.bool,
|
||||
};
|
||||
}
|
||||
|
||||
state = {
|
||||
selected: {},
|
||||
};
|
||||
}
|
||||
|
||||
handleOptionChange = e => {
|
||||
const { target: { value } } = e;
|
||||
const { target: { value } } = e
|
||||
|
||||
if (this.props.poll.get('multiple')) {
|
||||
const tmp = { ...this.state.selected };
|
||||
const tmp = { ...this.state.selected }
|
||||
if (tmp[value]) {
|
||||
delete tmp[value];
|
||||
delete tmp[value]
|
||||
} else {
|
||||
tmp[value] = true;
|
||||
tmp[value] = true
|
||||
}
|
||||
this.setState({ selected: tmp });
|
||||
this.setState({ selected: tmp })
|
||||
} else {
|
||||
const tmp = {};
|
||||
tmp[value] = true;
|
||||
this.setState({ selected: tmp });
|
||||
const tmp = {}
|
||||
tmp[value] = true
|
||||
this.setState({ selected: tmp })
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
handleVote = () => {
|
||||
if (this.props.disabled) return;
|
||||
if (this.props.disabled) return
|
||||
|
||||
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)));
|
||||
};
|
||||
this.props.dispatch(vote(this.props.poll.get('id'), Object.keys(this.state.selected)))
|
||||
}
|
||||
|
||||
handleRefresh = () => {
|
||||
if (this.props.disabled) return;
|
||||
renderOption(option, optionIndex) {
|
||||
const { poll, disabled } = this.props
|
||||
const { selected } = this.state
|
||||
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100
|
||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'))
|
||||
const active = !!selected[`${optionIndex}`]
|
||||
const showResults = poll.get('voted') || poll.get('expired')
|
||||
const multiple = poll.get('multiple')
|
||||
|
||||
this.props.dispatch(fetchPoll(this.props.poll.get('id')));
|
||||
};
|
||||
|
||||
renderOption (option, optionIndex) {
|
||||
const { poll, disabled } = this.props;
|
||||
const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100;
|
||||
const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count'));
|
||||
const active = !!this.state.selected[`${optionIndex}`];
|
||||
const showResults = poll.get('voted') || poll.get('expired');
|
||||
const multiple = poll.get('multiple');
|
||||
|
||||
let titleEmojified = option.get('title_emojified');
|
||||
let titleEmojified = option.get('title_emojified')
|
||||
if (!titleEmojified) {
|
||||
const emojiMap = makeEmojiMap(poll);
|
||||
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap);
|
||||
const emojiMap = makeEmojiMap(poll)
|
||||
titleEmojified = emojify(escapeTextContentForBrowser(option.get('title')), emojiMap)
|
||||
}
|
||||
|
||||
const chartClasses = classNames('poll__chart', {
|
||||
'poll__chart--leading': leading,
|
||||
});
|
||||
const chartClasses = cx({
|
||||
default: 1,
|
||||
positionAbsolute: 1,
|
||||
top0: 1,
|
||||
left0: 1,
|
||||
radiusSmall: 1,
|
||||
height100PC: 1,
|
||||
backgroundSubtle2: !leading,
|
||||
backgroundColorBrandLight: leading,
|
||||
})
|
||||
|
||||
const textClasses = classNames('poll__text', {
|
||||
selectable: !showResults,
|
||||
});
|
||||
|
||||
const inputClasses = classNames('poll__input', {
|
||||
const inputClasses = cx('poll__input', {
|
||||
'poll__input--checkbox': multiple,
|
||||
'poll__input--active': active,
|
||||
});
|
||||
})
|
||||
|
||||
const listItemClasses = cx({
|
||||
default: 1,
|
||||
flexRow: 1,
|
||||
paddingVertical10PX: showResults,
|
||||
marginBottom10PX: 1,
|
||||
border1PX: !showResults,
|
||||
borderColorSecondary: !showResults,
|
||||
circle: !showResults,
|
||||
cursorPointer: !showResults,
|
||||
backgroundSubtle_onHover: !showResults,
|
||||
backgroundSubtle: !showResults && active,
|
||||
})
|
||||
|
||||
const textContainerClasses = cx({
|
||||
default: 1,
|
||||
width100PC: 1,
|
||||
paddingHorizontal15PX: 1,
|
||||
paddingVertical10PX: !showResults,
|
||||
cursorPointer: !showResults,
|
||||
alignItemsCenter: !showResults,
|
||||
})
|
||||
|
||||
return (
|
||||
<li className='poll-item' key={option.get('title')}>
|
||||
<li className={listItemClasses} key={option.get('title')}>
|
||||
{
|
||||
showResults && (
|
||||
<Motion defaultStyle={{ width: 0 }} style={{ width: spring(percent, { stiffness: 180, damping: 12 }) }}>
|
||||
@@ -110,72 +135,99 @@ class Poll extends ImmutablePureComponent {
|
||||
)
|
||||
}
|
||||
|
||||
<label className={textClasses}>
|
||||
<input
|
||||
name='vote-options'
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
value={optionIndex}
|
||||
checked={active}
|
||||
onChange={this.handleOptionChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<label className={textContainerClasses}>
|
||||
<Text
|
||||
size='medium'
|
||||
color='primary'
|
||||
weight={leading ? 'bold' : 'normal'}
|
||||
className={[_s.displayFlex, _s.flexRow, _s.width100PC, _s.alignItemsCenter].join(' ')}
|
||||
>
|
||||
{
|
||||
!showResults &&
|
||||
<input
|
||||
name='vote-options'
|
||||
type={multiple ? 'checkbox' : 'radio'}
|
||||
value={optionIndex}
|
||||
checked={active}
|
||||
onChange={this.handleOptionChange}
|
||||
disabled={disabled}
|
||||
className={[_s.default, _s.marginRight10PX].join(' ')}
|
||||
/>
|
||||
}
|
||||
|
||||
{!showResults && <span className={inputClasses} />}
|
||||
{showResults && <span className='poll-item__number'>{Math.round(percent)}%</span>}
|
||||
{
|
||||
!showResults && <span className={inputClasses} />
|
||||
}
|
||||
|
||||
<span className='poll-item__text' dangerouslySetInnerHTML={{ __html: titleEmojified }} />
|
||||
<span dangerouslySetInnerHTML={{ __html: titleEmojified }} />
|
||||
|
||||
{
|
||||
showResults &&
|
||||
<span className={_s.marginLeftAuto}>
|
||||
{Math.round(percent)}%
|
||||
</span>
|
||||
}
|
||||
</Text>
|
||||
</label>
|
||||
</li>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { poll, intl } = this.props;
|
||||
render() {
|
||||
const { poll, intl } = this.props
|
||||
|
||||
if (!poll) return null;
|
||||
if (!poll) return null
|
||||
|
||||
const timeRemaining = poll.get('expired') ?
|
||||
intl.formatMessage(messages.closed)
|
||||
: <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />;
|
||||
const showResults = poll.get('voted') || poll.get('expired');
|
||||
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item);
|
||||
: <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />
|
||||
const showResults = poll.get('voted') || poll.get('expired')
|
||||
const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item)
|
||||
|
||||
return (
|
||||
<div className='poll'>
|
||||
<ul className='poll__list'>
|
||||
{poll.get('options').map((option, i) => this.renderOption(option, i))}
|
||||
<div className={[_s.default, _s.paddingHorizontal15PX, _s.paddingVertical10PX].join(' ')}>
|
||||
<ul className={[_s.default, _s.listStyleNone].join(' ')}>
|
||||
{
|
||||
poll.get('options').map((option, i) => this.renderOption(option, i))
|
||||
}
|
||||
</ul>
|
||||
|
||||
<div className='poll__footer'>
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter].join(' ')}>
|
||||
{
|
||||
!showResults &&
|
||||
<Button className='poll__button' disabled={disabled} onClick={this.handleVote} secondary>
|
||||
{intl.formatMessage(messages.vote)}
|
||||
<Button
|
||||
narrow
|
||||
className={_s.marginRight10PX}
|
||||
disabled={disabled}
|
||||
onClick={this.handleVote}
|
||||
>
|
||||
<Text color='inherit' size='small' className={_s.paddingHorizontal10PX}>
|
||||
{intl.formatMessage(messages.vote)}
|
||||
</Text>
|
||||
</Button>
|
||||
}
|
||||
{
|
||||
showResults && !this.props.disabled &&
|
||||
<span>
|
||||
<button className='poll__link' onClick={this.handleRefresh}>
|
||||
{intl.formatMessage(messages.refresh)}
|
||||
</button>
|
||||
·
|
||||
</span>
|
||||
}
|
||||
<FormattedMessage
|
||||
id='poll.total_votes'
|
||||
defaultMessage='{count, plural, one {# vote} other {# votes}}'
|
||||
values={{
|
||||
count: poll.get('votes_count'),
|
||||
}}
|
||||
/>
|
||||
{
|
||||
poll.get('expires_at') &&
|
||||
<span> · {timeRemaining}</span>
|
||||
}
|
||||
|
||||
<Text color='secondary'>
|
||||
<FormattedMessage
|
||||
id='poll.total_votes'
|
||||
defaultMessage='{count, plural, one {# vote} other {# votes}}'
|
||||
values={{
|
||||
count: poll.get('votes_count'),
|
||||
}}
|
||||
/>
|
||||
{
|
||||
poll.get('expires_at') &&
|
||||
<Fragment>
|
||||
<DotTextSeperator />
|
||||
<Text color='secondary' className={_s.marginLeft5PX}>
|
||||
{timeRemaining}
|
||||
</Text>
|
||||
</Fragment>
|
||||
}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
41
app/javascript/gabsocial/components/select.js
Normal file
41
app/javascript/gabsocial/components/select.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
|
||||
export default class Select extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
options: PropTypes.oneOf([
|
||||
ImmutablePropTypes.map,
|
||||
PropTypes.object,
|
||||
]),
|
||||
value: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
value,
|
||||
options,
|
||||
onChange
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
<div className={_s.default}>
|
||||
<select
|
||||
className={[_s.default, _s.outlineNone, _s.text, _s.border1PX, _s.borderColorSecondary, _s.paddingHorizontal15PX, _s.select].join(' ')}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
>
|
||||
{
|
||||
options.map(option => (
|
||||
<option key={`option-${option.value}`} value={option.value}>
|
||||
{option.title}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -165,8 +165,8 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
handleToggleMediaVisibility = () => {
|
||||
this.setState({ showMedia: !this.state.showMedia });
|
||||
};
|
||||
this.setState({ showMedia: !this.state.showMedia })
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.onClick) {
|
||||
@@ -178,8 +178,8 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
this.context.router.history.push(
|
||||
`/${this._properStatus().getIn(['account', 'acct'])}/posts/${this._properStatus().get('id')}`
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
handleExpandClick = e => {
|
||||
if (e.button === 0) {
|
||||
@@ -271,10 +271,18 @@ class Status extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
let media = null;
|
||||
let prepend, rebloggedByText, reblogContent;
|
||||
const {
|
||||
intl,
|
||||
hidden,
|
||||
featured,
|
||||
unread,
|
||||
showThread,
|
||||
group,
|
||||
promoted
|
||||
} = this.props
|
||||
|
||||
const { intl, hidden, featured, unread, showThread, group, promoted } = this.props;
|
||||
let media = null
|
||||
let prepend, rebloggedByText, reblogContent
|
||||
|
||||
// console.log("replies:", this.props.replies)
|
||||
|
||||
@@ -321,7 +329,7 @@ class Status extends ImmutablePureComponent {
|
||||
prepend = (
|
||||
<div className={[_s.default, _s.flexRow, _s.alignItemsCenter, _s.borderBottom1PX, _s.borderColorSecondary, _s.paddingVertical5PX, _s.paddingHorizontal15PX].join(' ')}>
|
||||
<Icon
|
||||
id='star'
|
||||
id='pin'
|
||||
width='10px'
|
||||
height='10px'
|
||||
className={_s.fillColorSecondary}
|
||||
@@ -460,12 +468,7 @@ class Status extends ImmutablePureComponent {
|
||||
|
||||
{prepend}
|
||||
|
||||
<div
|
||||
className={classNames('status', `status-${status.get('visibility')}`, {
|
||||
muted: this.props.muted,
|
||||
})}
|
||||
data-id={status.get('id')}
|
||||
>
|
||||
<div data-id={status.get('id')}>
|
||||
|
||||
<StatusHeader status={status} />
|
||||
|
||||
@@ -493,6 +496,7 @@ class Status extends ImmutablePureComponent {
|
||||
) */ }
|
||||
|
||||
<StatusActionBar status={status} account={account} {...other} />
|
||||
{ /* : todo : comment bar, comments */ }
|
||||
</div>
|
||||
</Block>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ import classNames from 'classnames/bind'
|
||||
import { openModal } from '../actions/modal'
|
||||
import { me, isStaff } from '../initial_state'
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container'
|
||||
import Icon from './icon'
|
||||
import Text from './text'
|
||||
import StatusActionBarItem from './status_action_bar_item'
|
||||
|
||||
const messages = defineMessages({
|
||||
@@ -117,6 +117,10 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleShareClick = () => {
|
||||
//
|
||||
}
|
||||
|
||||
render() {
|
||||
const { status, intl: { formatMessage } } = this.props
|
||||
|
||||
@@ -135,34 +139,6 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
|
||||
)
|
||||
|
||||
const items = [
|
||||
{
|
||||
title: formatMessage(messages.like),
|
||||
icon: 'like',
|
||||
active: !!status.get('favorited'),
|
||||
onClick: this.handleFavoriteClick,
|
||||
},
|
||||
{
|
||||
title: formatMessage(messages.comment),
|
||||
icon: 'comment',
|
||||
active: false,
|
||||
onClick: this.handleReplyClick,
|
||||
},
|
||||
{
|
||||
title: repostTitle,
|
||||
icon: (status.get('visibility') === 'private') ? 'lock' : 'repost',
|
||||
disabled: !publicStatus,
|
||||
active: !!status.get('reblogged'),
|
||||
onClick: this.handleRepostClick,
|
||||
},
|
||||
{
|
||||
title: formatMessage(messages.share),
|
||||
icon: 'share',
|
||||
active: false,
|
||||
onClick: this.handleFavoriteClick,
|
||||
},
|
||||
]
|
||||
|
||||
const hasInteractions = favoriteCount > 0 || replyCount > 0 || repostCount > 0
|
||||
const shouldCondense = (!!status.get('card') || status.get('media_attachments').size > 0) && !hasInteractions
|
||||
|
||||
@@ -186,9 +162,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
const interactionBtnClasses = cx({
|
||||
default: 1,
|
||||
text: 1,
|
||||
colorSecondary: 1,
|
||||
cursorPointer: 1,
|
||||
fontSize15PX: 1,
|
||||
fontWeightNormal: 1,
|
||||
marginRight10PX: 1,
|
||||
paddingVertical5PX: 1,
|
||||
@@ -199,37 +173,64 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||
{
|
||||
hasInteractions &&
|
||||
<div className={[_s.default, _s.flexRow, _s.paddingHorizontal5PX].join(' ')}>
|
||||
{favoriteCount > 0 &&
|
||||
{
|
||||
favoriteCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
{favoriteCount}
|
||||
Likes
|
||||
<Text color='secondary'>
|
||||
{favoriteCount}
|
||||
Likes
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
{replyCount > 0 &&
|
||||
{
|
||||
replyCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
{replyCount}
|
||||
Comments
|
||||
<Text color='secondary'>
|
||||
{replyCount}
|
||||
Comments
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
{repostCount > 0 &&
|
||||
{
|
||||
repostCount > 0 &&
|
||||
<button className={interactionBtnClasses}>
|
||||
{repostCount}
|
||||
Reposts
|
||||
<Text color='secondary'>
|
||||
{repostCount}
|
||||
Reposts
|
||||
</Text>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div className={innerContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.paddingVertical2PX, _s.width100PC].join(' ')}>
|
||||
{
|
||||
items.map((item, i) => (
|
||||
<StatusActionBarItem key={`status-action-bar-item-${i}`} {...item} />
|
||||
))
|
||||
}
|
||||
<StatusActionBarItem
|
||||
title={formatMessage(messages.like)}
|
||||
icon='like'
|
||||
active={!!status.get('favorited')}
|
||||
onClick={this.handleFavoriteClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
title={formatMessage(messages.comment)}
|
||||
icon='comment'
|
||||
onClick={this.handleReplyClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
title={repostTitle}
|
||||
icon={(status.get('visibility') === 'private') ? 'lock' : 'repost'}
|
||||
disabled={!publicStatus}
|
||||
active={!!status.get('reblogged')}
|
||||
onClick={this.handleRepostClick}
|
||||
/>
|
||||
<StatusActionBarItem
|
||||
title={formatMessage(messages.share)}
|
||||
icon='share'
|
||||
onClick={this.handleShareClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className={[_s.default, _s.borderTop1PX, _s.borderColorSecondary, _s.paddingTop10PX, _s.marginBottom10PX].join(' ')}>
|
||||
{ /* <ComposeFormContainer statusId={status.get('id')} shouldCondense /> */ }
|
||||
{ /* <ComposeFormContainer statusId={status.get('id')} shouldCondense /> */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -29,7 +29,7 @@ export default class StatusActionBarItem extends PureComponent {
|
||||
paddingHorizontal10PX: 1,
|
||||
width100PC: 1,
|
||||
radiusSmall: 1,
|
||||
outlineFocusBrand: 1,
|
||||
outlineNone: 1,
|
||||
backgroundTransparent: 1,
|
||||
backgroundSubtle_onHover: 1,
|
||||
colorSecondary: 1,
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import classNames from 'classnames/bind'
|
||||
import { me } from '../initial_state'
|
||||
import ComposeFormContainer from '../features/compose/containers/compose_form_container'
|
||||
import Block from './block'
|
||||
import Heading from './heading'
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
const messages = defineMessages({
|
||||
createPost: { id: 'column_header.create_post', defaultMessage: 'Create Post' },
|
||||
})
|
||||
@@ -25,6 +28,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
||||
intl: PropTypes.object.isRequired,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
size: PropTypes.number,
|
||||
modal: PropTypes.bool,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
@@ -32,7 +36,23 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { account, size, intl, ...rest } = this.props
|
||||
const {
|
||||
account,
|
||||
size,
|
||||
intl,
|
||||
modal,
|
||||
...rest
|
||||
} = this.props
|
||||
|
||||
if (modal) {
|
||||
return (
|
||||
<section className={_s.default}>
|
||||
<div className={[_s.default, _s.flexRow].join(' ')}>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<section className={[_s.default, _s.marginBottom15PX].join(' ')}>
|
||||
@@ -42,7 +62,7 @@ class TimelineComposeBlock extends ImmutablePureComponent {
|
||||
{intl.formatMessage(messages.createPost)}
|
||||
</Heading>
|
||||
</div>
|
||||
<div className={[_s.default, _s.flexRow, _s.paddingVertical15PX, _s.paddingHorizontal15PX].join(' ')}>
|
||||
<div className={[_s.default, _s.flexRow, _s.paddingHorizontal15PX, _s.paddingVertical15PX].join(' ')}>
|
||||
<ComposeFormContainer {...rest} />
|
||||
</div>
|
||||
</Block>
|
||||
|
||||
Reference in New Issue
Block a user