This commit is contained in:
mgabdev
2020-12-15 19:31:30 -05:00
parent de0c977950
commit 75d52c841e
129 changed files with 2559 additions and 910 deletions

View File

@@ -1,75 +1,94 @@
import React from 'react'
import PropTypes from 'prop-types'
import { defineMessages, injectIntl } from 'react-intl'
import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { length } from 'stringz'
import { isMobile } from '../../../utils/is_mobile'
import { countableText } from '../../ui/util/counter'
import {
CX,
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
MODAL_COMPOSE,
POPOVER_COMPOSE_POST_DESTINATION,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import { openModal } from '../../../actions/modal'
import { openPopover } from '../../../actions/popover'
import Avatar from '../../../components/avatar'
import Button from '../../../components/button'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import PollForm from './poll_form'
import SchedulePostButton from './schedule_post_button'
import SpoilerButton from './spoiler_button'
import ExpiresPostButton from './expires_post_button'
import RichTextEditorButton from './rich_text_editor_button'
import StatusContainer from '../../../containers/status_container'
import StatusVisibilityButton from './status_visibility_button'
import UploadButton from './media_upload_button'
import UploadForm from './upload_form'
import Input from '../../../components/input'
import Text from '../../../components/text'
import Icon from '../../../components/icon'
import ComposeExtraButtonList from './compose_extra_button_list'
import Text from '../../../components/text'
class ComposeDestinationHeader extends ImmutablePureComponent {
handleOnClick = () => {
this.props.onOpenPopover(this.desinationBtn)
}
handleOnExpand = () => {
this.props.onOpenModal()
}
setDestinationBtn = (c) => {
this.desinationBtn = c
}
render() {
const { account } = this.props
const { account, isModal } = this.props
const title = 'Post to timeline'
return (
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary, _s.mb5, _s.mt5, _s.px15, _s.w100PC, _s.h40PX].join(' ')}>
<Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}>
<Button
isNarrow
radiusSmall
backgroundColor='tertiary'
color='primary'
onClick={this.handleOnClick}
>
<Text color='inherit' size='small' className={_s.jcCenter}>
{title}
<Icon id='caret-down' size='8px' className={_s.ml5} />
</Text>
</Button>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.bgPrimary, _s.w100PC, _s.h40PX, _s.pr15].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.pl15, _s.flexGrow1, _s.mrAuto, _s.h40PX].join(' ')}>
<Avatar account={account} size={28} />
<div className={[_s.ml15].join(' ')}>
<Button
isNarrow
isOutline
radiusSmall
buttonRef={this.setDestinationBtn}
backgroundColor='secondary'
color='primary'
onClick={this.handleOnClick}
className={[_s.border1PX, _s.borderColorPrimary].join(' ')}
>
<Text color='inherit' size='small' className={_s.jcCenter}>
{title}
<Icon id='caret-down' size='8px' className={_s.ml5} />
</Text>
</Button>
</div>
</div>
{
!isModal &&
<Button
isText
isNarrow
backgroundColor='none'
color='tertiary'
icon='fullscreen'
onClick={this.handleOnExpand}
/>
}
</div>
)
}
}
const mapDispatchToProps = (dispatch) => ({
onOpenModal() {
dispatch(openModal(MODAL_COMPOSE))
},
onOpenPopover(targetRef) {
dispatch(openPopover(POPOVER_COMPOSE_POST_DESTINATION, {
targetRef,
position: 'bottom',
}))
},
})
ComposeDestinationHeader.propTypes = {
account: ImmutablePropTypes.map,
isModal: PropTypes.bool,
onOpenModal: PropTypes.func.isRequired,
onOpenPopover: PropTypes.func.isRequired,
}
export default ComposeDestinationHeader
export default connect(null, mapDispatchToProps)(ComposeDestinationHeader)

View File

@@ -22,14 +22,14 @@ class ComposeExtraButton extends React.PureComponent {
const containerClasses = CX({
d: 1,
mr5: 1,
jcCenter: 1,
h40PX: 1,
mr5: 1,
})
const btnClasses = CX({
d: 1,
circle: 1,
circle: small,
noUnderline: 1,
font: 1,
cursorPointer: 1,
@@ -37,21 +37,25 @@ class ComposeExtraButton extends React.PureComponent {
outlineNone: 1,
bgTransparent: 1,
flexRow: 1,
aiCenter: 1,
// jcCenter: !small,
bgSubtle_onHover: !active,
bgBrandLight: active,
py10: 1,
px10: 1,
px10: small,
radiusSmall: !small,
})
const iconClasses = CX(iconClassName, {
const iconClasses = CX(active ? null : iconClassName, {
cSecondary: !active,
cWhite: active,
mr10: 1,
mr10: !small,
py2: small,
ml10: small,
ml10: !small,
px2: small,
})
const iconSize = !small ? '18px' : '16px'
const iconSize = '16px'
const textColor = !active ? 'primary' : 'white'
return (
@@ -65,13 +69,13 @@ class ComposeExtraButton extends React.PureComponent {
backgroundColor='none'
iconClassName={iconClasses}
icon={icon}
iconSize={iconSize}
iconSize='16px'
buttonRef={!children ? buttonRef : undefined}
>
{ children }
{
!small &&
<Text color={textColor} weight='medium' className={[_s.pr5].join(' ')}>
<Text color={textColor} weight='medium' className={[_s.pr10].join(' ')}>
{title}
</Text>
}

View File

@@ -6,6 +6,7 @@ import {
} from '../../../constants'
import Responsive from '../../ui/util/responsive_component'
import ResponsiveClassesComponent from '../../ui/util/responsive_classes_component'
import Text from '../../../components/text'
import EmojiPickerButton from './emoji_picker_button'
import PollButton from './poll_button'
import SchedulePostButton from './schedule_post_button'
@@ -22,6 +23,7 @@ class ComposeExtraButtonList extends React.PureComponent {
state = {
height: initialState.height,
width: initialState.width,
}
componentDidMount() {
@@ -31,9 +33,9 @@ class ComposeExtraButtonList extends React.PureComponent {
}
handleResize = () => {
const { height } = getWindowDimension()
const { height, width } = getWindowDimension()
this.setState({ height })
this.setState({ height, width })
}
componentWillUnmount() {
@@ -48,26 +50,33 @@ class ComposeExtraButtonList extends React.PureComponent {
edit,
hidePro,
isModal,
isStandalone,
formLocation,
} = this.props
const { height } = this.state
const { height, width } = this.state
const small = (height <= 660 || isModal) && !isStandalone
const isXS = width <= BREAKPOINT_EXTRA_SMALL
const isStandalone = formLocation === 'standalone'
const isTimeline = formLocation === 'timeline'
const small = (!isModal && isXS && !isStandalone) || isTimeline
console.log("small, formLocation:", small, formLocation)
const containerClasses = CX({
d: 1,
w100PC: 1,
bgPrimary: 1,
px15: 1,
py10: 1,
px5: 1,
py5: 1,
mb10: 1,
mtAuto: 1,
boxShadowBlockY: 1,
topLeftRadiusSmall: 1,
radiusSmall: 1,
borderTop1PX: 1,
borderBottom1PX: 1,
boxShadowBlock: 1,
borderColorSecondary: 1,
topRightRadiusSmall: 1,
flexRow: small,
overflowXScroll: small,
noScrollbar: small,
flexWrap: 1,
flexRow: (small || !isTimeline || isXS) && !isStandalone,
jcSpaceAround: isXS,
})
return (
@@ -79,8 +88,8 @@ class ComposeExtraButtonList extends React.PureComponent {
<SpoilerButton small={small} />
{ !hidePro && !edit && <SchedulePostButton small={small} /> }
{ !hidePro && !edit && <ExpiresPostButton small={small} /> }
{ !hidePro && <RichTextEditorButton small={small} /> }
</div>
{ !hidePro && !isXS && <RichTextEditorButton small={small} /> }
</div>
)
}
}
@@ -90,7 +99,7 @@ ComposeExtraButtonList.propTypes = {
edit: PropTypes.bool,
isMatch: PropTypes.bool,
isModal: PropTypes.bool,
isStandalone: PropTypes.bool,
formLocation: PropTypes.string,
}
export default ComposeExtraButtonList

View File

@@ -1,5 +1,6 @@
import React from 'react'
import PropTypes from 'prop-types'
import { NavLink } from 'react-router-dom'
import { defineMessages, injectIntl } from 'react-intl'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
@@ -11,7 +12,6 @@ import {
MAX_POST_CHARACTER_COUNT,
ALLOWED_AROUND_SHORT_CODE,
BREAKPOINT_EXTRA_SMALL,
BREAKPOINT_EXTRA_EXTRA_SMALL,
BREAKPOINT_MEDIUM,
} from '../../../constants'
import AutosuggestTextbox from '../../../components/autosuggest_textbox'
@@ -62,80 +62,70 @@ class ComposeForm extends ImmutablePureComponent {
}
handleComposeFocus = () => {
this.setState({
composeFocused: true,
});
this.setState({ composeFocused: true })
}
handleKeyDown = (e) => {
if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) {
this.handleSubmit();
this.handleSubmit()
}
}
handleClick = (e) => {
const { isStandalone, isModalOpen, shouldCondense } = this.props
const { isModalOpen, shouldCondense } = this.props
if (!this.form) return false
if (e.target) {
if (e.target.classList.contains('react-datepicker__time-list-item')) return false
}
if (!this.form.contains(e.target)) {
this.handleClickOutside()
} else {
// : todo :
// if mobile go to /compose else openModal
if (!isStandalone && !isModalOpen && !shouldCondense) {
this.props.openComposeModal()
return false
}
}
}
handleClickOutside = () => {
const { shouldCondense, scheduledAt, text, isModalOpen } = this.props;
const condensed = shouldCondense && !text;
const { shouldCondense, scheduledAt, text, isModalOpen } = this.props
const condensed = shouldCondense && !text
if (condensed && scheduledAt && !isModalOpen) { //Reset scheduled date if condensing
this.props.setScheduledAt(null);
this.props.setScheduledAt(null)
}
this.setState({
composeFocused: false,
});
this.setState({ composeFocused: false })
}
handleSubmit = () => {
// if (this.props.text !== this.autosuggestTextarea.textbox.value) {
// Something changed the text inside the textarea (e.g. browser extensions like Grammarly)
// Update the state to match the current text
// this.props.onChange(this.autosuggestTextarea.textbox.value);
// this.props.onChange(this.autosuggestTextarea.textbox.value)
// }
// Submit disabled:
const { isSubmitting, isChangingUpload, isUploading, anyMedia, groupId, autoJoinGroup } = this.props
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('');
const fulltext = [this.props.spoilerText, countableText(this.props.text)].join('')
if (isSubmitting || isUploading || isChangingUpload || length(fulltext) > MAX_POST_CHARACTER_COUNT || (fulltext.length !== 0 && fulltext.trim().length === 0 && !anyMedia)) {
return;
return
}
this.props.onSubmit(groupId, this.props.replyToId, this.context.router, autoJoinGroup)
}
onSuggestionsClearRequested = () => {
this.props.onClearSuggestions();
this.props.onClearSuggestions()
}
onSuggestionsFetchRequested = (token) => {
this.props.onFetchSuggestions(token);
this.props.onFetchSuggestions(token)
}
onSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['text']);
this.props.onSuggestionSelected(tokenStart, token, value, ['text'])
}
onSpoilerSuggestionSelected = (tokenStart, token, value) => {
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text']);
this.props.onSuggestionSelected(tokenStart, token, value, ['spoiler_text'])
}
handleChangeSpoilerText = (value) => {
@@ -143,11 +133,11 @@ class ComposeForm extends ImmutablePureComponent {
}
componentDidMount() {
document.addEventListener('click', this.handleClick, false);
document.addEventListener('click', this.handleClick, false)
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClick, false);
document.removeEventListener('click', this.handleClick, false)
}
componentDidUpdate(prevProps) {
@@ -156,24 +146,24 @@ class ComposeForm extends ImmutablePureComponent {
// This statement does several things:
// - If we're beginning a reply, and,
// - Replying to zero or one users, places the cursor at the end of the textbox.
// - Replying to more than one user, selects any usernames past the first;
// - Replying to more than one user, selects any usernames past the first
// this provides a convenient shortcut to drop everyone else from the conversation.
if (this.props.focusDate !== prevProps.focusDate) {
let selectionEnd, selectionStart;
let selectionEnd, selectionStart
if (this.props.preselectDate !== prevProps.preselectDate) {
selectionEnd = this.props.text.length;
selectionStart = this.props.text.search(/\s/) + 1;
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;
selectionStart = this.props.caretPosition
selectionEnd = this.props.caretPosition
} else {
selectionEnd = this.props.text.length;
selectionStart = selectionEnd;
selectionEnd = this.props.text.length
selectionStart = selectionEnd
}
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd);
// this.autosuggestTextarea.textbox.focus();
// this.autosuggestTextarea.textbox.setSelectionRange(selectionStart, selectionEnd)
// this.autosuggestTextarea.textbox.focus()
}
}
@@ -190,7 +180,6 @@ class ComposeForm extends ImmutablePureComponent {
intl,
account,
onPaste,
showSearch,
anyMedia,
shouldCondense,
autoFocus,
@@ -208,218 +197,151 @@ class ComposeForm extends ImmutablePureComponent {
isSubmitting,
isPro,
hidePro,
isStandalone,
dontOpenModal,
formLocation,
} = this.props
const disabled = isSubmitting
const text = [this.props.spoilerText, countableText(this.props.text)].join('');
const text = [this.props.spoilerText, countableText(this.props.text)].join('')
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
const shouldAutoFocus = autoFocus && !showSearch && !isMobile(window.innerWidth)
const shouldAutoFocus = autoFocus && !isMobile(window.innerWidth)
const parentContainerClasses = CX({
const containerClasses = CX({
d: 1,
w100PC: 1,
flexRow: !shouldCondense,
pb10: !shouldCondense,
pb10: 1,
calcMaxH410PX: 1,
minH200PX: isModalOpen && isMatch,
overflowYScroll: 1,
boxShadowBlock: 1,
borderTop1PX: 1,
borderColorSecondary: 1,
})
const childContainerClasses = CX({
d: 1,
flexWrap: 1,
overflowHidden: 1,
flex1: 1,
minH28PX: 1,
py2: shouldCondense,
aiEnd: shouldCondense,
flexRow: shouldCondense,
radiusSmall: shouldCondense,
bgSubtle: shouldCondense,
px5: shouldCondense,
})
const actionsContainerClasses = CX({
d: 1,
flexRow: 1,
aiCenter: !shouldCondense,
aiStart: shouldCondense,
mt10: !shouldCondense,
px10: !shouldCondense,
mlAuto: shouldCondense,
flexWrap: !shouldCondense,
})
const commentPublishBtnClasses = CX({
d: 1,
jcCenter: 1,
displayNone: length(this.props.text) === 0,
})
const textbox = (
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
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}
isModalOpen={isModalOpen}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
)
if (shouldCondense) {
return (
<div className={[_s.d, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.w100PC, _s.aiCenter].join(' ')}>
<div className={[_s.d, _s.mr10].join(' ')}>
<Avatar account={account} size={28} noHover />
<Avatar account={account} size={30} noHover />
</div>
<div
className={[_s.d, _s.flexWrap, _s.overflowHidden, _s.flex1, _s.minH28PX, _s.py2, _s.aiEnd, _s.flexRow, _s.radiusSmall, _s.bgSubtle, _s.px5].join(' ')}
className={[_s.d, _s.flexWrap, _s.overflowHidden, _s.flex1, _s.minH28PX, _s.py5, _s.aiEnd, _s.flexRow, _s.radiusSmall, _s.bgSubtle, _s.px5].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage(messages.commentPlaceholder)}
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}
isPro={isPro}
isEdit={!!edit}
id='comment-composer'
/>
<div className={[_s.d, _s.flexRow, _s.aiStart, _s.mlAuto].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<div className={commentPublishBtnClasses}>
<Button
isNarrow
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px10}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.post)}
</Button>
</div>
</div>
</div>
{ textbox }
{ isMatch && <ComposeFormSubmitButton type='comment' /> }
</div>
</div>
{
(isUploading || anyMedia) &&
(isUploading || anyMedia) && isMatch &&
<div className={[_s.d, _s.w100PC, _s.pl35, _s.mt5].join(' ')}>
<UploadForm replyToId={replyToId} isModalOpen={isModalOpen} />
<UploadForm isModalOpen={isModalOpen} />
</div>
}
</div>
)
}
if (isStandalone || isModalOpen) {
return (
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgTertiary].join(' ')}>
<div className={[_s.d, _s.pb10, _s.calcMaxH370PX, _s.overflowYScroll, _s.boxShadowBlock, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<ComposeDestinationHeader account={account} />
<div
className={[_s.d, _s.bgPrimary, _s.boxShadowBlock, _s.w100PC, _s.minH200PX, _s.pb10, _s.borderBottom1PX, _s.borderTop1PX, _s.borderColorSecondary].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={reduxReplyToId} isChild />
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
<AutosuggestTextbox
ref={(isModalOpen && shouldCondense) ? null : this.setAutosuggestTextarea}
placeholder={intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
disabled={disabled}
value={this.props.text}
valueMarkdown={this.props.markdown}
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}
isPro={isPro}
isEdit={!!edit}
id='main-composer'
/>
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<UploadForm replyToId={replyToId} isModalOpen={isModalOpen} />
</div>
}
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm replyToId={replyToId} />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={quoteOfId} isChild />
</div>
}
</div>
</div>
{ !isModalOpen && <ComposeFormSubmitButton /> }
<ComposeExtraButtonList isStandalone={isStandalone} isMatch={isMatch} hidePro={hidePro} edit={edit} isModal={isModalOpen} />
</div>
)
}
return (
<div
className={[_s.d, _s.w100PC, _s.pb10, _s.flexWrap, _s.overflowHidden, _s.flex1].join(' ')}
ref={this.setForm}
onClick={this.handleClick}
>
<Text className={[_s.d, _s.px15, _s.pt15, _s.pb10].join(' ')} size='medium' color='tertiary'>
{intl.formatMessage((shouldCondense || !!reduxReplyToId) && isMatch ? messages.commentPlaceholder : messages.placeholder)}
</Text>
<div className={[_s.d, _s.flexRow, _s.aiCenter, _s.mt5, _s.px10, _s.flexWrap].join(' ')}>
<UploadButton />
<EmojiPickerButton isMatch={isMatch} />
<PollButton />
<MoreButton />
<ComposeFormSubmitButton />
<div className={[_s.d, _s.w100PC, _s.flexGrow1, _s.bgPrimary].join(' ')}>
<div className={[_s.d, _s.calcMaxH410PX, _s.overflowYScroll].join(' ')}>
<Responsive min={BREAKPOINT_EXTRA_SMALL}>
<ComposeDestinationHeader account={account} isModal={isModalOpen} />
</Responsive>
<div className={containerClasses} ref={this.setForm} onClick={this.handleClick}>
{
!!reduxReplyToId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={reduxReplyToId} isChild />
</div>
}
{
!!spoiler &&
<div className={[_s.d, _s.px15, _s.py10, _s.borderBottom1PX, _s.borderColorSecondary].join(' ')}>
<Input
placeholder={intl.formatMessage(messages.spoiler_placeholder)}
value={this.props.spoilerText}
onChange={this.handleChangeSpoilerText}
disabled={!this.props.spoiler}
prependIcon='warning'
maxLength={256}
id='cw-spoiler-input'
/>
</div>
}
{ textbox }
{
(isUploading || anyMedia) &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<div className={[_s.d, _s.borderTop1PX, _s.borderColorSecondary].join(' ')} />
<UploadForm />
</div>
}
{
!edit && hasPoll &&
<div className={[_s.d, _s.px15, _s.mt5].join(' ')}>
<PollForm />
</div>
}
{
!!quoteOfId && isModalOpen && isMatch &&
<div className={[_s.d, _s.px15, _s.py10, _s.mt5].join(' ')}>
<StatusContainer contextType='compose' id={quoteOfId} isChild />
</div>
}
</div>
</div>
<div className={[_s.d, _s.posAbs, _s.z2, _s.left0, _s.right0, _s.bottom0, _s.top0].join(' ')} />
<div className={[_s.d, _s.px10].join(' ')}>
<ComposeExtraButtonList formLocation={formLocation} isMatch={isMatch} hidePro={hidePro} edit={edit} isModal={isModalOpen} />
</div>
{
(!disabledButton || isModalOpen) && isMatch &&
<div className={[_s.d, _s.mb10, _s.px10].join(' ')}>
<ComposeFormSubmitButton type='block' />
</div>
}
<Responsive max={BREAKPOINT_EXTRA_SMALL}>
{
formLocation === 'timeline' &&
<NavLink to='/compose' className={[_s.d, _s.z4, _s.posAbs, _s.top0, _s.left0, _s.right0, _s.bottom0].join(' ')} />
}
</Responsive>
</div>
)
}
@@ -450,9 +372,7 @@ ComposeForm.propTypes = {
onFetchSuggestions: PropTypes.func.isRequired,
onSuggestionSelected: PropTypes.func.isRequired,
onChangeSpoilerText: PropTypes.func.isRequired,
openComposeModal: PropTypes.func.isRequired,
onPaste: PropTypes.func.isRequired,
showSearch: PropTypes.bool,
anyMedia: PropTypes.bool,
shouldCondense: PropTypes.bool,
autoFocus: PropTypes.bool,
@@ -466,11 +386,7 @@ ComposeForm.propTypes = {
isPro: PropTypes.bool,
hidePro: PropTypes.bool,
autoJoinGroup: PropTypes.bool,
isStandalone: PropTypes.bool,
}
ComposeForm.defaultProps = {
showSearch: false,
formLocation: PropTypes.string,
}
export default injectIntl(ComposeForm)

View File

@@ -1,30 +1,80 @@
import React from 'react'
import PropTypes from 'prop-types'
import { CX } from '../../../constants'
import { connect } from 'react-redux'
import { defineMessages, injectIntl } from 'react-intl'
import { length } from 'stringz'
import { countableText } from '../../ui/util/counter'
import { submitCompose } from '../../../actions/compose'
import {
CX,
MAX_POST_CHARACTER_COUNT,
} from '../../../constants'
import Button from '../../../components/button'
import Text from '../../../components/text'
class ComposeFormSubmitButton extends React.PureComponent {
handleSubmit = () => {
}
render() {
const {
intl,
title,
active,
small,
disabledButton,
type,
edit,
text,
isSubmitting,
isChangingUpload,
isUploading,
anyMedia,
quoteOfId,
scheduledAt,
hasPoll,
} = this.props
const disabledButton = isSubmitting || isUploading || isChangingUpload || length(text) > MAX_POST_CHARACTER_COUNT || (length(text.trim()) === 0 && !anyMedia)
if (type === 'comment') {
const commentPublishBtnClasses = CX({
d: 1,
jcCenter: 1,
displayNone: disabledButton,
})
return (
<div className={[_s.d, _s.flexRow, _s.aiStart, _s.mlAuto].join(' ')}>
<div className={[_s.d, _s.flexRow, _s.mrAuto].join(' ')}>
<div className={commentPublishBtnClasses}>
<Button
isNarrow
radiusSmall
onClick={this.handleSubmit}
isDisabled={disabledButton}
className={_s.px15}
>
{intl.formatMessage(scheduledAt ? messages.schedulePost : messages.post)}
</Button>
</div>
</div>
</div>
)
}
const containerClasses = CX({
d: 1,
mr5: 1,
jcCenter: 1,
h40PX: 1,
})
const btnClasses = CX({
d: 1,
circle: 1,
radiusSmall: 1,
noUnderline: 1,
font: 1,
cursorPointer: 1,
@@ -37,31 +87,33 @@ class ComposeFormSubmitButton extends React.PureComponent {
py10: 1,
px10: 1,
})
const iconClasses = CX({
cSecondary: !active,
cWhite: active,
mr10: 1,
py2: small,
ml10: small,
})
const iconSize = !small ? '18px' : '16px'
const textColor = !active ? 'primary' : 'white'
let backgroundColor, color
if (disabledButton) {
backgroundColor = 'tertiary'
color = 'tertiary'
} else if (type === 'navigation') {
backgroundColor = 'white'
color = 'brand'
} else {
backgroundColor = 'brand'
color = 'white'
}
return (
<div className={containerClasses}>
<div className={[_s.d, _s.w100PC, _s.py10, _s.px10].join(' ')}>
<div className={[_s.d, _s.w100PC].join(' ')}>
<Button
isBlock
radiusSmall
isDisabled={disabledButton}
backgroundColor={disabledButton ? 'secondary' : 'brand'}
color={disabledButton ? 'tertiary' : 'white'}
backgroundColor={backgroundColor}
color={color}
className={[_s.fs15PX, _s.px15, _s.flexGrow1, _s.mlAuto].join(' ')}
onClick={this.handleSubmit}
>
<Text color='inherit' weight='medium' align='center'>
post
<Text color='inherit' size='medium' weight='bold' align='center'>
{intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.postEdit : messages.post)}
</Text>
</Button>
</div>
@@ -71,9 +123,32 @@ class ComposeFormSubmitButton extends React.PureComponent {
}
// {intl.formatMessage(scheduledAt ? messages.schedulePost : edit ? messages.postEdit : messages.post)}
const messages = defineMessages({
post: { id: 'compose_form.post', defaultMessage: 'Post' },
postEdit: { id: 'compose_form.post_edit', defaultMessage: 'Post Edit' },
schedulePost: { id: 'compose_form.schedule_post', defaultMessage: 'Schedule Post' },
})
const mapStateToProps = (state) => ({
edit: state.getIn(['compose', 'id']) !== null,
text: state.getIn(['compose', 'text']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
quoteOfId: state.getIn(['compose', 'quote_of_id']),
scheduledAt: state.getIn(['compose', 'scheduled_at']),
hasPoll: state.getIn(['compose', 'poll']),
})
const mapDispatchToProps = (dispatch) => ({
onSubmit(groupId, replyToId = null, router, isStandalone, autoJoinGroup) {
dispatch(submitCompose(groupId, replyToId, router, isStandalone, autoJoinGroup))
}
})
ComposeFormSubmitButton.propTypes = {
type: PropTypes.oneOf(['header', 'block', 'comment'])
type: PropTypes.oneOf(['header', 'navigation', 'block', 'comment'])
}
export default ComposeFormSubmitButton
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ComposeFormSubmitButton))

View File

@@ -48,7 +48,7 @@ class ExpiresPostButton extends React.PureComponent {
}
const messages = defineMessages({
expires: { id: 'expiration.title', defaultMessage: 'Add status expiration' },
expires: { id: 'expiration.title', defaultMessage: 'Status expiration' },
})
const mapStateToProps = (state) => ({

View File

@@ -19,7 +19,7 @@ class Upload extends ImmutablePureComponent {
}
state = {
hovered: false,
hovering: false,
focused: false,
dirtyDescription: null,
}
@@ -45,11 +45,11 @@ class Upload extends ImmutablePureComponent {
}
handleMouseEnter = () => {
this.setState({ hovered: true })
this.setState({ hovering: true })
}
handleMouseLeave = () => {
this.setState({ hovered: false })
this.setState({ hovering: false })
}
handleInputFocus = () => {
@@ -75,66 +75,60 @@ class Upload extends ImmutablePureComponent {
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 { hovering } = this.state
const descriptionContainerClasses = CX({
d: 1,
posAbs: 1,
right0: 1,
bottom0: 1,
left0: 1,
mt5: 1,
mb5: 1,
ml5: 1,
mr5: 1,
displayNone: !active,
})
const active = hovering || this.state.focused
const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''
return (
<div
tabIndex='0'
className={[_s.d, _s.w50PC, _s.px5, _s.py5].join(' ')}
className={[_s.d, _s.w100PC, _s.mt10].join(' ')}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
onClick={this.handleClick}
role='button'
>
<div className={[_s.d, _s.radiusSmall, _s.overflowHidden, _s.h158PX].join(' ')}>
<div className={[_s.d, _s.radiusSmall, _s.borderColorSecondary, _s.border1PX, _s.overflowHidden, _s.maxH100VH, _s.minH106PX].join(' ')}>
<Image
className={[_s.d, _s.h158PX].join(' ')}
className={[_s.d, _s.minH106PX, _s.maxH100VH].join(' ')}
src={media.get('preview_url')}
/>
{ hovering && <div className={[_s.d, _s.posAbs, _s.z2, _s.top0, _s.bottom0, _s.right0, _s.left0, _s.bgBlackOpaquest].join(' ')} /> }
{
media.get('type') === 'gifv' &&
<div className={[_s.d, _s.posAbs, _s.z2, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.ml10, _s.mt10, _s.top0, _s.left0].join(' ')}>
<div className={[_s.d, _s.posAbs, _s.z3, _s.radiusSmall, _s.bgBlackOpaque, _s.px5, _s.py5, _s.ml10, _s.mt10, _s.bottom0, _s.right0].join(' ')}>
<Text size='extraSmall' color='white' weight='medium'>GIF</Text>
</div>
}
<Button
backgroundColor='black'
color='white'
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='close'
iconSize='10px'
iconClassName={_s.inherit}
className={[_s.top0, _s.right0, _s.posAbs, _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 className={[_s.d, _s.posAbs, _s.px15, _s.pt15, _s.z3, _s.flexRow, _s.top0, _s.left0, _s.right0].join(' ')}>
{
active &&
<div className={[_s.d, _s.flexGrow1, _s.mr15].join(' ')}>
<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>
}
<Button
backgroundColor='black'
color='white'
title={intl.formatMessage(messages.delete)}
onClick={this.handleUndoClick}
icon='close'
iconSize='10px'
iconClassName={_s.inherit}
className={[_s.mlAuto, _s.px10].join(' ')}
/>
</div>
</div>

View File

@@ -11,7 +11,7 @@ class SensitiveMediaButton extends React.PureComponent {
const { active, disabled, onClick, intl } = this.props
return (
<div className={[_s.d, _s.aiStart, _s.px5].join(' ')}>
<div className={[_s.d, _s.aiStart, _s.px5, _s.py10].join(' ')}>
<Switch
id='mark-sensitive'
type='checkbox'

View File

@@ -18,23 +18,16 @@ class UploadForm extends ImmutablePureComponent {
return (
<div className={_s.d}>
{ isUploading && <ProgressBar small progress={uploadProgress} /> }
<div className={[_s.d, _s.flexRow, _s.flexWrap].join(' ')}>
{
mediaIds.map(id => (
<Upload id={id} key={id} />
))
}
{mediaIds.map(id => (
<Upload id={id} key={id} />
))}
</div>
{
!mediaIds.isEmpty() &&
<SensitiveMediaButton />
}
{
isUploading &&
<ProgressBar small progress={uploadProgress} />
}
{ !mediaIds.isEmpty() && <SensitiveMediaButton /> }
{ isUploading && <ProgressBar small progress={uploadProgress} /> }
</div>
)
}
@@ -48,7 +41,6 @@ const mapStateToProps = (state) => ({
})
UploadForm.propTypes = {
isModalOpen: PropTypes.bool,
isUploading: PropTypes.bool,
mediaIds: ImmutablePropTypes.list.isRequired,
uploadProgress: PropTypes.number,