Progress
This commit is contained in:
@@ -8,7 +8,6 @@ import Button from '../../../components/button';
|
||||
import { autoPlayGif, me, isStaff } from '../../../initial_state';
|
||||
import Avatar from '../../../components/avatar';
|
||||
import { shortNumberFormat } from '../../../utils/numbers';
|
||||
import Dropdown from '../../../components/dropdown_menu'
|
||||
import ProfileInfoPanel from './profile_info_panel/profile_info_panel';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { openModal } from '../../../../actions/modal';
|
||||
import Dropdown from '../../../../components/dropdown_menu'
|
||||
import { meUsername } from '../../../../initial_state';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -5,27 +5,30 @@ export default class CharacterCounter extends PureComponent {
|
||||
static propTypes = {
|
||||
text: PropTypes.string.isRequired,
|
||||
max: PropTypes.number.isRequired,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
render () {
|
||||
const radius = 12
|
||||
const { text, max, small } = this.props
|
||||
const actualRadius = small ? '10' : '16'
|
||||
const radius = small ? 8 : 12
|
||||
const circumference = 2 * Math.PI * radius
|
||||
const diff = length(this.props.text) / this.props.max
|
||||
const diff = length(text) / max
|
||||
const dashoffset = circumference * (1 - diff)
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.marginRight10PX, _s.justifyContentCenter, _s.alignItemsCenter].join(' ')}>
|
||||
<svg width="32" height="32" viewBox="0 0 32 32">
|
||||
<circle fill='none' cx="16" cy="16" r="12" fill="none" stroke="#e6e6e6" strokeWidth="2" />
|
||||
<svg width={actualRadius * 2} height={actualRadius * 2} viewBox={`0 0 ${actualRadius * 2} ${actualRadius * 2}`}>
|
||||
<circle fill='none' cx={actualRadius} cy={actualRadius} r={radius} fill="none" stroke="#e6e6e6" strokeWidth="2" />
|
||||
<circle style={{
|
||||
// transform: 'rotate(-90deg)',
|
||||
strokeDashoffset: dashoffset,
|
||||
strokeDasharray: circumference,
|
||||
}}
|
||||
fill='none'
|
||||
cx="16"
|
||||
cy="16"
|
||||
r="12"
|
||||
cx={actualRadius}
|
||||
cy={actualRadius}
|
||||
r={radius}
|
||||
strokeWidth="2"
|
||||
strokeLinecap='round'
|
||||
stroke='#21cf7a'
|
||||
|
||||
@@ -9,6 +9,7 @@ export default class ComposeExtraButton extends PureComponent {
|
||||
disabled: PropTypes.bool,
|
||||
onClick: PropTypes.func,
|
||||
icon: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -24,18 +25,26 @@ export default class ComposeExtraButton extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { title, disabled, onClick, icon, children } = this.props
|
||||
const { title, disabled, onClick, icon, children, small } = this.props
|
||||
const { hovering } = this.state
|
||||
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
marginRight10PX: !small,
|
||||
marginRight2PX: small,
|
||||
})
|
||||
|
||||
const btnClasses = cx({
|
||||
default: 1,
|
||||
circle: 1,
|
||||
flexRow: 1,
|
||||
paddingVertical10PX: 1,
|
||||
paddingHorizontal10PX: 1,
|
||||
cursorPointer: 1,
|
||||
backgroundSubtle: !hovering,
|
||||
backgroundSubtle2: hovering,
|
||||
paddingVertical10PX: !small,
|
||||
paddingHorizontal10PX: !small,
|
||||
paddingVertical5PX: small,
|
||||
paddingHorizontal5PX: small,
|
||||
})
|
||||
|
||||
const titleClasses = cx({
|
||||
@@ -49,8 +58,10 @@ export default class ComposeExtraButton extends PureComponent {
|
||||
displayNone: !hovering,
|
||||
})
|
||||
|
||||
const iconSize = !!small ? '12px' : '18px'
|
||||
|
||||
return (
|
||||
<div className={[_s.default, _s.marginRight10PX].join(' ')}>
|
||||
<div className={containerClasses}>
|
||||
<button
|
||||
className={btnClasses}
|
||||
title={title}
|
||||
@@ -59,10 +70,13 @@ export default class ComposeExtraButton extends PureComponent {
|
||||
onMouseEnter={() => this.handleOnMouseEnter()}
|
||||
onMouseLeave={() => this.handleOnMouseLeave()}
|
||||
>
|
||||
<Icon id={icon} width='18px' height='18px' className={_s.fillcolorSecondary} />
|
||||
<span className={titleClasses}>
|
||||
{title}
|
||||
</span>
|
||||
<Icon id={icon} width={iconSize} height={iconSize} className={_s.fillcolorSecondary} />
|
||||
{
|
||||
!small &&
|
||||
<span className={titleClasses}>
|
||||
{title}
|
||||
</span>
|
||||
}
|
||||
</button>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineMessages, injectIntl } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { length } from 'stringz';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import classNames from 'classnames';
|
||||
import classNames from 'classnames/bind'
|
||||
import CharacterCounter from '../character_counter';
|
||||
import UploadForm from '../upload_form';
|
||||
import ReplyIndicatorContainer from '../../containers/reply_indicator_container';
|
||||
@@ -18,6 +18,7 @@ import SchedulePostDropdown from '../../components/schedule_post_dropdown';
|
||||
import QuotedStatusPreviewContainer from '../../containers/quoted_status_preview_container';
|
||||
import Icon from '../../../../components/icon';
|
||||
import Button from '../../../../components/button';
|
||||
import Avatar from '../../../../components/avatar'
|
||||
import { isMobile } from '../../../../utils/is_mobile';
|
||||
import { countableText } from '../../util/counter';
|
||||
|
||||
@@ -32,6 +33,8 @@ const messages = defineMessages({
|
||||
schedulePost: { id: 'compose_form.schedule_post', defaultMessage: 'Schedule Post' }
|
||||
});
|
||||
|
||||
const cx = classNames.bind(_s)
|
||||
|
||||
export default
|
||||
@injectIntl
|
||||
class ComposeForm extends ImmutablePureComponent {
|
||||
@@ -49,6 +52,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
edit: PropTypes.bool.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
suggestions: ImmutablePropTypes.list,
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
status: ImmutablePropTypes.map,
|
||||
spoiler: PropTypes.bool,
|
||||
privacy: PropTypes.string,
|
||||
spoilerText: PropTypes.string,
|
||||
@@ -164,7 +169,7 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
document.removeEventListener("click", this.handleClick, false);
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps) {
|
||||
componentDidUpdate(prevProps) {
|
||||
if (!this.autosuggestTextarea) return;
|
||||
|
||||
// This statement does several things:
|
||||
@@ -176,13 +181,13 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
let selectionEnd, selectionStart;
|
||||
|
||||
if (this.props.preselectDate !== prevProps.preselectDate) {
|
||||
selectionEnd = this.props.text.length;
|
||||
selectionEnd = this.props.text.length;
|
||||
selectionStart = this.props.text.search(/\s/) + 1;
|
||||
} else if (typeof this.props.caretPosition === 'number') {
|
||||
selectionStart = this.props.caretPosition;
|
||||
selectionEnd = this.props.caretPosition;
|
||||
selectionEnd = this.props.caretPosition;
|
||||
} else {
|
||||
selectionEnd = this.props.text.length;
|
||||
selectionEnd = this.props.text.length;
|
||||
selectionStart = selectionEnd;
|
||||
}
|
||||
|
||||
@@ -211,30 +216,65 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
this.props.onPickEmoji(position, data, needsSpace);
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, onPaste, showSearch, anyMedia, shouldCondense, autoFocus, isModalOpen, quoteOfId, edit, scheduledAt } = this.props;
|
||||
render() {
|
||||
const {
|
||||
intl,
|
||||
account,
|
||||
onPaste,
|
||||
showSearch,
|
||||
anyMedia,
|
||||
shouldCondense,
|
||||
autoFocus,
|
||||
isModalOpen,
|
||||
quoteOfId,
|
||||
edit,
|
||||
scheduledAt
|
||||
} = this.props
|
||||
const condensed = shouldCondense && !this.props.text && !this.state.composeFocused;
|
||||
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('');
|
||||
const disabledButton = disabled || this.props.isUploading || this.props.isChangingUpload || length(text) > maxPostCharacterCount || (text.length !== 0 && text.trim().length === 0 && !anyMedia);
|
||||
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
|
||||
|
||||
const composeClassNames = classNames({
|
||||
'compose-form': true,
|
||||
'condensed': condensed,
|
||||
});
|
||||
const containerClasses = cx({
|
||||
default: 1,
|
||||
flexGrow1: 1,
|
||||
flexRow: shouldCondense,
|
||||
radiusSmall: shouldCondense,
|
||||
backgroundSubtle: shouldCondense,
|
||||
paddingHorizontal5PX: shouldCondense,
|
||||
})
|
||||
|
||||
const actionsContainerClasses = cx({
|
||||
default: 1,
|
||||
flexRow: 1,
|
||||
alignItemsCenter: 1,
|
||||
marginTop10PX: !shouldCondense,
|
||||
})
|
||||
|
||||
const avatarContainerClasses = cx({
|
||||
default: 1,
|
||||
marginRight10PX: 1,
|
||||
marginTop5PX: shouldCondense,
|
||||
})
|
||||
|
||||
const avatarSize = shouldCondense ? 28 : 46
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[_s.default, _s.flexGrow1].join(' ')}
|
||||
ref={this.setForm}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{ /* <WarningContainer /> */ }
|
||||
<div className={[_s.default, _s.flexRow, _s.width100PC].join(' ')}>
|
||||
<div className={avatarContainerClasses}>
|
||||
<Avatar account={account} size={avatarSize} />
|
||||
</div>
|
||||
<div
|
||||
className={containerClasses}
|
||||
ref={this.setForm}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{ /* <WarningContainer /> */}
|
||||
|
||||
{ /* !shouldCondense && <ReplyIndicatorContainer /> */ }
|
||||
{ /* !shouldCondense && <ReplyIndicatorContainer /> */}
|
||||
|
||||
{ /*
|
||||
{ /*
|
||||
<div className={`spoiler-input ${this.props.spoiler ? 'spoiler-input--visible' : ''}`}>
|
||||
<AutosuggestTextbox
|
||||
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
|
||||
@@ -254,63 +294,68 @@ class ComposeForm extends ImmutablePureComponent {
|
||||
</div>
|
||||
*/ }
|
||||
|
||||
{ /*
|
||||
{ /*
|
||||
<div className='emoji-picker-wrapper'>
|
||||
<EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} />
|
||||
</div> */ }
|
||||
|
||||
<AutosuggestTextbox
|
||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
disabled={disabled}
|
||||
value={this.props.text}
|
||||
onChange={this.handleChange}
|
||||
suggestions={this.props.suggestions}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.handleComposeFocus}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
onPaste={onPaste}
|
||||
autoFocus={shouldAutoFocus}
|
||||
textarea
|
||||
>
|
||||
{ /*
|
||||
<AutosuggestTextbox
|
||||
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
|
||||
placeholder={intl.formatMessage(messages.placeholder)}
|
||||
disabled={disabled}
|
||||
value={this.props.text}
|
||||
onChange={this.handleChange}
|
||||
suggestions={this.props.suggestions}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onFocus={this.handleComposeFocus}
|
||||
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
|
||||
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
|
||||
onSuggestionSelected={this.onSuggestionSelected}
|
||||
onPaste={onPaste}
|
||||
autoFocus={shouldAutoFocus}
|
||||
small={shouldCondense}
|
||||
textarea
|
||||
>
|
||||
{ /*
|
||||
!condensed &&
|
||||
<div className='compose-form__modifiers'>
|
||||
<UploadForm />
|
||||
{!edit && <PollFormContainer />}
|
||||
</div>
|
||||
*/ }
|
||||
</AutosuggestTextbox>
|
||||
</AutosuggestTextbox>
|
||||
|
||||
{ /* quoteOfId && <QuotedStatusPreviewContainer id={quoteOfId} /> */ }
|
||||
{ /* quoteOfId && <QuotedStatusPreviewContainer id={quoteOfId} /> */}
|
||||
|
||||
{
|
||||
/* !condensed && */
|
||||
<div className={[_s.default, _s.flexRow, _s.marginTop10PX].join(' ')}>
|
||||
<div className={actionsContainerClasses}>
|
||||
<div className={[_s.default, _s.flexRow, _s.marginRightAuto].join(' ')}>
|
||||
<UploadButton />
|
||||
<UploadButton small={shouldCondense} />
|
||||
{
|
||||
!edit && <PollButton />
|
||||
!edit && <PollButton small={shouldCondense} />
|
||||
}
|
||||
<PrivacyDropdown />
|
||||
<SpoilerButton />
|
||||
<SchedulePostDropdown position={isModalOpen ? 'top' : undefined} />
|
||||
{
|
||||
!shouldCondense &&
|
||||
<PrivacyDropdown />
|
||||
}
|
||||
<SpoilerButton small={shouldCondense} />
|
||||
<SchedulePostDropdown small={shouldCondense} position={isModalOpen ? 'top' : undefined} />
|
||||
</div>
|
||||
<CharacterCounter max={maxPostCharacterCount} text={text} />
|
||||
<Button
|
||||
className={[_s.fontSize15PX, _s.paddingHorizontal15PX].join(' ')}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={disabledButton}
|
||||
>
|
||||
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
|
||||
</Button>
|
||||
<CharacterCounter max={maxPostCharacterCount} text={text} small={shouldCondense} />
|
||||
{
|
||||
!shouldCondense &&
|
||||
<Button
|
||||
className={[_s.fontSize15PX, _s.paddingHorizontal15PX].join(' ')}
|
||||
onClick={this.handleSubmit}
|
||||
disabled={disabledButton}
|
||||
>
|
||||
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.publish)}
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ class PollButton extends PureComponent {
|
||||
active: PropTypes.bool,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
small: PropTypes.bool,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
@@ -45,7 +46,7 @@ class PollButton extends PureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, active, unavailable, disabled } = this.props;
|
||||
const { intl, active, unavailable, disabled, small } = this.props;
|
||||
|
||||
if (unavailable) return null;
|
||||
|
||||
@@ -55,6 +56,7 @@ class PollButton extends PureComponent {
|
||||
disabled={disabled}
|
||||
onClick={this.handleClick}
|
||||
icon='poll'
|
||||
small={small}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ class SchedulePostDropdown extends PureComponent {
|
||||
isPro: PropTypes.bool,
|
||||
onOpenProUpgradeModal: PropTypes.func.isRequired,
|
||||
position: PropTypes.string,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleToggle = () => {
|
||||
@@ -73,7 +74,7 @@ class SchedulePostDropdown extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intl, date, isPro, position } = this.props
|
||||
const { intl, date, isPro, position, small } = this.props
|
||||
|
||||
const open = !!date
|
||||
const datePickerDisabled = !isPro
|
||||
@@ -87,6 +88,7 @@ class SchedulePostDropdown extends PureComponent {
|
||||
icon='calendar'
|
||||
title={intl.formatMessage(messages.schedule_status)}
|
||||
onClick={this.handleToggle}
|
||||
small={small}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
|
||||
@@ -28,6 +28,7 @@ class SpoilerButton extends PureComponent {
|
||||
static propTypes = {
|
||||
active: PropTypes.bool,
|
||||
intl: PropTypes.map,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleClick = (e) => {
|
||||
@@ -36,13 +37,14 @@ class SpoilerButton extends PureComponent {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { active, intl } = this.props
|
||||
const { active, intl, small } = this.props
|
||||
|
||||
return (
|
||||
<ComposeExtraButton
|
||||
title={intl.formatMessage(messages.title)}
|
||||
icon='warning'
|
||||
onClick={this.handleClick}
|
||||
small={small}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||
resetFileKey: PropTypes.number,
|
||||
acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
small: PropTypes.bool,
|
||||
}
|
||||
|
||||
handleChange = (e) => {
|
||||
@@ -56,7 +57,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, resetFileKey, unavailable, disabled, acceptContentTypes } = this.props
|
||||
const { intl, resetFileKey, unavailable, disabled, acceptContentTypes, small } = this.props
|
||||
|
||||
if (unavailable) return null
|
||||
|
||||
@@ -66,6 +67,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||
disabled={disabled}
|
||||
onClick={this.handleClick}
|
||||
icon='media'
|
||||
small={small}
|
||||
>
|
||||
<label>
|
||||
<span className={_s.displayNone}>{intl.formatMessage(messages.upload)}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import ComposeForm from '../components/compose_form';
|
||||
import ComposeForm from '../components/compose_form'
|
||||
import {
|
||||
changeCompose,
|
||||
submitCompose,
|
||||
@@ -9,66 +9,74 @@ import {
|
||||
insertEmojiCompose,
|
||||
uploadCompose,
|
||||
changeScheduledAt,
|
||||
} from '../../../actions/compose';
|
||||
} from '../../../actions/compose'
|
||||
import { me } from '../../../initial_state'
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
edit: state.getIn(['compose', 'id']) !== null,
|
||||
text: state.getIn(['compose', 'text']),
|
||||
suggestions: state.getIn(['compose', 'suggestions']),
|
||||
spoiler: state.getIn(['compose', 'spoiler']),
|
||||
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||
privacy: state.getIn(['compose', 'privacy']),
|
||||
focusDate: state.getIn(['compose', 'focusDate']),
|
||||
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
||||
quoteOfId: state.getIn(['compose', 'quote_of_id']),
|
||||
scheduledAt: state.getIn(['compose', 'scheduled_at']),
|
||||
});
|
||||
const mapStateToProps = (state, { status }) => {
|
||||
|
||||
// : todo :
|
||||
//everything needs to be in relation to if there's a status or not
|
||||
|
||||
return {
|
||||
edit: state.getIn(['compose', 'id']) !== null,
|
||||
text: state.getIn(['compose', 'text']),
|
||||
suggestions: state.getIn(['compose', 'suggestions']),
|
||||
spoiler: state.getIn(['compose', 'spoiler']),
|
||||
spoilerText: state.getIn(['compose', 'spoiler_text']),
|
||||
privacy: state.getIn(['compose', 'privacy']),
|
||||
focusDate: state.getIn(['compose', 'focusDate']),
|
||||
caretPosition: state.getIn(['compose', 'caretPosition']),
|
||||
preselectDate: state.getIn(['compose', 'preselectDate']),
|
||||
isSubmitting: state.getIn(['compose', 'is_submitting']),
|
||||
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
|
||||
isUploading: state.getIn(['compose', 'is_uploading']),
|
||||
showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
|
||||
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
isModalOpen: state.get('modal').modalType === 'COMPOSE',
|
||||
quoteOfId: state.getIn(['compose', 'quote_of_id']),
|
||||
scheduledAt: state.getIn(['compose', 'scheduled_at']),
|
||||
account: state.getIn(['accounts', me]),
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
onChange (text) {
|
||||
dispatch(changeCompose(text));
|
||||
onChange(text) {
|
||||
dispatch(changeCompose(text))
|
||||
},
|
||||
|
||||
onSubmit (router, group) {
|
||||
dispatch(submitCompose(router, group));
|
||||
onSubmit(router, group) {
|
||||
dispatch(submitCompose(router, group))
|
||||
},
|
||||
|
||||
onClearSuggestions () {
|
||||
dispatch(clearComposeSuggestions());
|
||||
onClearSuggestions() {
|
||||
dispatch(clearComposeSuggestions())
|
||||
},
|
||||
|
||||
onFetchSuggestions (token) {
|
||||
dispatch(fetchComposeSuggestions(token));
|
||||
onFetchSuggestions(token) {
|
||||
dispatch(fetchComposeSuggestions(token))
|
||||
},
|
||||
|
||||
onSuggestionSelected (position, token, suggestion, path) {
|
||||
dispatch(selectComposeSuggestion(position, token, suggestion, path));
|
||||
onSuggestionSelected(position, token, suggestion, path) {
|
||||
dispatch(selectComposeSuggestion(position, token, suggestion, path))
|
||||
},
|
||||
|
||||
onChangeSpoilerText (checked) {
|
||||
dispatch(changeComposeSpoilerText(checked));
|
||||
onChangeSpoilerText(checked) {
|
||||
dispatch(changeComposeSpoilerText(checked))
|
||||
},
|
||||
|
||||
onPaste (files) {
|
||||
dispatch(uploadCompose(files));
|
||||
onPaste(files) {
|
||||
dispatch(uploadCompose(files))
|
||||
},
|
||||
|
||||
onPickEmoji (position, data, needsSpace) {
|
||||
dispatch(insertEmojiCompose(position, data, needsSpace));
|
||||
onPickEmoji(position, data, needsSpace) {
|
||||
dispatch(insertEmojiCompose(position, data, needsSpace))
|
||||
},
|
||||
|
||||
setScheduledAt (date) {
|
||||
dispatch(changeScheduledAt(date));
|
||||
setScheduledAt(date) {
|
||||
dispatch(changeScheduledAt(date))
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||
return Object.assign({}, ownProps, {
|
||||
@@ -77,4 +85,4 @@ function mergeProps(stateProps, dispatchProps, ownProps) {
|
||||
})
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm);
|
||||
export default connect(mapStateToProps, mapDispatchToProps, mergeProps)(ComposeForm)
|
||||
|
||||
@@ -2,7 +2,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { debounce } from 'lodash';
|
||||
import { fetchFavouritedStatuses, expandFavouritedStatuses } from '../../actions/favourites';
|
||||
import { fetchFavoritedStatuses, expandFavoritedStatuses } from '../../actions/favourites';
|
||||
import { meUsername } from '../../initial_state';
|
||||
import StatusList from '../../components/status_list';
|
||||
import ColumnIndicator from '../../components/column_indicator';
|
||||
@@ -29,11 +29,11 @@ class Favourites extends ImmutablePureComponent {
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchFavouritedStatuses());
|
||||
this.props.dispatch(fetchFavoritedStatuses());
|
||||
}
|
||||
|
||||
handleLoadMore = debounce(() => {
|
||||
this.props.dispatch(expandFavouritedStatuses());
|
||||
this.props.dispatch(expandFavoritedStatuses());
|
||||
}, 300, { leading: true })
|
||||
|
||||
render () {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import AccountContainer from '../../../containers/account_container';
|
||||
import ScrollableList from '../../../components/scrollable_list';
|
||||
import Dropdown from '../../../components/dropdown_menu'
|
||||
|
||||
const mapStateToProps = (state, { params: { id } }) => ({
|
||||
group: state.getIn(['groups', id]),
|
||||
|
||||
@@ -2,7 +2,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import Dropdown from '../../../../components/dropdown_menu'
|
||||
import Button from '../../../../components/button';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -1,24 +1,27 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import classNames from 'classnames';
|
||||
import { connectGroupStream } from '../../../actions/streaming';
|
||||
import { expandGroupTimeline } from '../../../actions/timelines';
|
||||
import StatusListContainer from '../../../containers/status_list_container';
|
||||
import ColumnSettingsContainer from './containers/column_settings_container';
|
||||
import Icon from '../../../components/icon';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import { injectIntl, defineMessages } from 'react-intl'
|
||||
import { Link } from 'react-router-dom'
|
||||
import classNames from 'classnames'
|
||||
import { connectGroupStream } from '../../../actions/streaming'
|
||||
import { expandGroupTimeline } from '../../../actions/timelines'
|
||||
import StatusListContainer from '../../../containers/status_list_container'
|
||||
import ColumnSettingsContainer from './containers/column_settings_container'
|
||||
import Icon from '../../../components/icon'
|
||||
import ColumnIndicator from '../../../components/column_indicator'
|
||||
|
||||
const messages = defineMessages({
|
||||
tabLatest: { id: 'group.timeline.tab_latest', defaultMessage: 'Latest' },
|
||||
show: { id: 'group.timeline.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'group.timeline.hide_settings', defaultMessage: 'Hide settings' },
|
||||
});
|
||||
tabLatest: { id: 'group.timeline.tab_latest', defaultMessage: 'Latest' },
|
||||
show: { id: 'group.timeline.show_settings', defaultMessage: 'Show settings' },
|
||||
hide: { id: 'group.timeline.hide_settings', defaultMessage: 'Hide settings' },
|
||||
empty: { id: 'empty_column.group', defaultMessage: 'There is nothing in this group yet. When members of this group post new statuses, they will appear here.' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state, props) => ({
|
||||
group: state.getIn(['groups', props.params.id]),
|
||||
relationships: state.getIn(['group_relationships', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
|
||||
});
|
||||
group: state.getIn(['groups', props.params.id]),
|
||||
relationships: state.getIn(['group_relationships', props.params.id]),
|
||||
hasUnread: state.getIn(['timelines', `group:${props.params.id}`, 'unread']) > 0,
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps)
|
||||
@@ -26,105 +29,71 @@ export default
|
||||
class GroupTimeline extends ImmutablePureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
router: PropTypes.object,
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
relationships: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
params: PropTypes.object.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
hasUnread: PropTypes.bool,
|
||||
group: PropTypes.oneOfType([ImmutablePropTypes.map, PropTypes.bool]),
|
||||
relationships: ImmutablePropTypes.map,
|
||||
intl: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
collapsed: true,
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { dispatch } = this.props;
|
||||
const { id } = this.props.params;
|
||||
componentDidMount() {
|
||||
const { dispatch } = this.props
|
||||
const { id } = this.props.params
|
||||
|
||||
dispatch(expandGroupTimeline(id));
|
||||
dispatch(expandGroupTimeline(id))
|
||||
|
||||
this.disconnect = dispatch(connectGroupStream(id));
|
||||
this.disconnect = dispatch(connectGroupStream(id))
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.disconnect) {
|
||||
this.disconnect();
|
||||
this.disconnect = null;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
if (this.disconnect) {
|
||||
this.disconnect()
|
||||
this.disconnect = null
|
||||
}
|
||||
}
|
||||
|
||||
handleLoadMore = maxId => {
|
||||
const { id } = this.props.params;
|
||||
this.props.dispatch(expandGroupTimeline(id, { maxId }));
|
||||
const { id } = this.props.params
|
||||
this.props.dispatch(expandGroupTimeline(id, { maxId }))
|
||||
}
|
||||
|
||||
handleToggleClick = (e) => {
|
||||
e.stopPropagation();
|
||||
this.setState({ collapsed: !this.state.collapsed });
|
||||
}
|
||||
e.stopPropagation()
|
||||
this.setState({ collapsed: !this.state.collapsed })
|
||||
}
|
||||
|
||||
render () {
|
||||
const { columnId, group, relationships, account, intl } = this.props;
|
||||
const { collapsed } = this.state;
|
||||
const { id } = this.props.params;
|
||||
render() {
|
||||
const { columnId, group, relationships, account, intl } = this.props
|
||||
const { collapsed } = this.state
|
||||
const { id } = this.props.params
|
||||
|
||||
if (typeof group === 'undefined' || !relationships) {
|
||||
return (<ColumnIndicator type='loading' />);
|
||||
} else if (group === false) {
|
||||
return (<ColumnIndicator type='missing' />);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
relationships.get('member') &&
|
||||
<TimelineComposeBlock size={46} group={group} shouldCondense={true} autoFocus={false} />
|
||||
}
|
||||
|
||||
<div className='group__feed'>
|
||||
<div className="column-header__wrapper">
|
||||
<h1 className="column-header">
|
||||
<Link to={`/groups/${id}`} className={classNames('btn grouped active')}>
|
||||
{intl.formatMessage(messages.tabLatest)}
|
||||
</Link>
|
||||
|
||||
<div className='column-header__buttons'>
|
||||
<button
|
||||
className={classNames('column-header__button', { 'active': !collapsed })}
|
||||
title={intl.formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-label={intl.formatMessage(collapsed ? messages.show : messages.hide)}
|
||||
aria-pressed={collapsed ? 'false' : 'true'}
|
||||
onClick={this.handleToggleClick}
|
||||
><Icon id='sliders' /></button>
|
||||
</div>
|
||||
</h1>
|
||||
{!collapsed && <div className='column-header__collapsible'>
|
||||
<div className='column-header__collapsible-inner'>
|
||||
<div className='column-header__collapsible__extra'>
|
||||
<ColumnSettingsContainer />
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
|
||||
<StatusListContainer
|
||||
alwaysPrepend
|
||||
scrollKey={`group_timeline-${columnId}`}
|
||||
timelineId={`group:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
group={group}
|
||||
withGroupAdmin={relationships && relationships.get('admin')}
|
||||
emptyMessage={<FormattedMessage id='empty_column.group' defaultMessage='There is nothing in this group yet. When members of this group post new statuses, they will appear here.' />}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
if (typeof group === 'undefined' || !relationships) {
|
||||
return (<ColumnIndicator type='loading' />)
|
||||
} else if (group === false) {
|
||||
return (<ColumnIndicator type='missing' />)
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusListContainer
|
||||
alwaysPrepend
|
||||
scrollKey={`group_timeline-${columnId}`}
|
||||
timelineId={`group:${id}`}
|
||||
onLoadMore={this.handleLoadMore}
|
||||
group={group}
|
||||
withGroupAdmin={relationships && relationships.get('admin')}
|
||||
emptyMessage={intl.formatMessage(messages.empty)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { default } from './pinned_statuses';
|
||||
@@ -1,49 +0,0 @@
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { fetchPinnedStatuses } from '../../actions/pin_statuses';
|
||||
import { meUsername } from '../../initial_state';
|
||||
import StatusList from '../../components/status_list/status_list';
|
||||
import ColumnIndicator from '../../components/column_indicator';
|
||||
|
||||
const mapStateToProps = (state, { params: { username } }) => {
|
||||
return {
|
||||
isMyAccount: (username.toLowerCase() === meUsername.toLowerCase()),
|
||||
statusIds: state.getIn(['status_lists', 'pins', 'items']),
|
||||
hasMore: !!state.getIn(['status_lists', 'pins', 'next']),
|
||||
};
|
||||
};;
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps)
|
||||
class PinnedStatuses extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
statusIds: ImmutablePropTypes.list.isRequired,
|
||||
hasMore: PropTypes.bool.isRequired,
|
||||
isMyAccount: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
componentWillMount () {
|
||||
this.props.dispatch(fetchPinnedStatuses());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { statusIds, hasMore, isMyAccount } = this.props;
|
||||
|
||||
if (!isMyAccount) {
|
||||
return <ColumnIndicator type='missing' />
|
||||
}
|
||||
|
||||
return (
|
||||
<StatusList
|
||||
statusIds={statusIds}
|
||||
scrollKey='pinned_statuses'
|
||||
hasMore={hasMore}
|
||||
emptyMessage={<FormattedMessage id='pinned_statuses.none' defaultMessage='No pins to show.' />}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import ComposeFormContainer from '../../compose/containers/compose_form_container';
|
||||
import NotificationsContainer from '../../../containers/notifications_container';
|
||||
import LoadingBarContainer from '../../../containers/loading_bar_container';
|
||||
import ModalContainer from '../../../containers/modal_container';
|
||||
import ModalRoot from '../../../components/modal/modal_root'
|
||||
|
||||
export default class Compose extends PureComponent {
|
||||
|
||||
@@ -10,7 +10,7 @@ export default class Compose extends PureComponent {
|
||||
<div>
|
||||
<ComposeFormContainer />
|
||||
<NotificationsContainer />
|
||||
<ModalContainer />
|
||||
<ModalRoot />
|
||||
<LoadingBarContainer className='loading-bar' />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -3,7 +3,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { openModal } from '../../../../actions/modal';
|
||||
import { me, isStaff } from '../../../../initial_state';
|
||||
import Dropdown from '../../../../components/dropdown_menu';
|
||||
import IconButton from '../../../../components/icon_button';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
||||
@@ -15,7 +15,8 @@ import { clearHeight } from '../../actions/height_cache'
|
||||
import { openModal } from '../../actions/modal'
|
||||
import WrappedRoute from './util/wrapped_route'
|
||||
import NotificationsContainer from '../../containers/notifications_container'
|
||||
import ModalContainer from '../../containers/modal_container'
|
||||
import ModalRoot from '../../components/modal/modal_root'
|
||||
import PopoverRoot from '../../components/popover/popover_root'
|
||||
import UploadArea from '../../components/upload_area'
|
||||
// import TrendsPanel from './components/trends_panel'
|
||||
// import { WhoToFollowPanel } from '../../components/panel'
|
||||
@@ -80,7 +81,6 @@ const mapStateToProps = state => ({
|
||||
isComposing: state.getIn(['compose', 'is_composing']),
|
||||
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
|
||||
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
|
||||
dropdownMenuIsOpen: state.getIn(['dropdown_menu', 'openId']) !== null,
|
||||
})
|
||||
|
||||
const keyMap = {
|
||||
@@ -243,7 +243,6 @@ class UI extends PureComponent {
|
||||
hasMediaAttachments: PropTypes.bool,
|
||||
location: PropTypes.object,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
}
|
||||
|
||||
state = {
|
||||
@@ -504,7 +503,7 @@ class UI extends PureComponent {
|
||||
|
||||
render() {
|
||||
const { draggingOver } = this.state
|
||||
const { children, location, dropdownMenuIsOpen } = this.props
|
||||
const { children, location } = this.props
|
||||
|
||||
const handlers = me ? {
|
||||
help: this.handleHotkeyToggleHelp,
|
||||
@@ -532,12 +531,7 @@ class UI extends PureComponent {
|
||||
attach={window}
|
||||
focused
|
||||
>
|
||||
<div
|
||||
ref={this.setRef}
|
||||
style={{
|
||||
pointerEvents: dropdownMenuIsOpen ? 'none' : null
|
||||
}}
|
||||
>
|
||||
<div ref={this.setRef}>
|
||||
<SwitchingArea
|
||||
location={location}
|
||||
onLayoutChange={this.handleLayoutChange}
|
||||
@@ -546,7 +540,8 @@ class UI extends PureComponent {
|
||||
</SwitchingArea>
|
||||
|
||||
<NotificationsContainer />
|
||||
<ModalContainer />
|
||||
<ModalRoot />
|
||||
<PopoverRoot />
|
||||
<UploadArea active={draggingOver} onClose={this.closeUploadModal} />
|
||||
</div>
|
||||
</HotKeys>
|
||||
|
||||
@@ -58,10 +58,6 @@ export function Status() {
|
||||
return import(/* webpackChunkName: "features/status" */'../../status')
|
||||
}
|
||||
|
||||
export function PinnedStatuses() {
|
||||
return import(/* webpackChunkName: "features/pinned_statuses" */'../../pinned_statuses')
|
||||
}
|
||||
|
||||
export function AccountTimeline() {
|
||||
return import(/* webpackChunkName: "features/account_timeline" */'../../account_timeline')
|
||||
}
|
||||
@@ -90,7 +86,7 @@ export function GenericNotFound() {
|
||||
return import(/* webpackChunkName: "features/generic_not_found" */'../../generic_not_found')
|
||||
}
|
||||
|
||||
export function FavouritedStatuses() {
|
||||
export function FavoritedStatuses() {
|
||||
return import(/* webpackChunkName: "features/favourited_statuses" */'../../favourited_statuses')
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user