diff options
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose')
8 files changed, 65 insertions, 110 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/character_counter.js b/app/javascript/flavours/glitch/features/compose/components/character_counter.js new file mode 100644 index 000000000..0ecfc9141 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/components/character_counter.js @@ -0,0 +1,25 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { length } from 'stringz'; + +export default class CharacterCounter extends React.PureComponent { + + static propTypes = { + text: PropTypes.string.isRequired, + max: PropTypes.number.isRequired, + }; + + checkRemainingText (diff) { + if (diff < 0) { + return <span className='character-counter character-counter--over'>{diff}</span>; + } + + return <span className='character-counter'>{diff}</span>; + } + + render () { + const diff = this.props.max - length(this.props.text); + return this.checkRemainingText(diff); + } + +} diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 3d9002fe4..6e07998ec 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -15,6 +15,8 @@ import { countableText } from 'flavours/glitch/util/counter'; import OptionsContainer from '../containers/options_container'; import Publisher from './publisher'; import TextareaIcons from './textarea_icons'; +import { maxChars } from 'flavours/glitch/util/initial_state'; +import CharacterCounter from './character_counter'; const messages = defineMessages({ placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, @@ -119,14 +121,8 @@ class ComposeForm extends ImmutablePureComponent { // Submit unless there are media with missing descriptions if (mediaDescriptionConfirmation && onMediaDescriptionConfirm && media && media.some(item => !item.get('description'))) { - const firstWithoutDescription = media.findIndex(item => !item.get('description')); - if (uploadForm) { - const inputs = uploadForm.querySelectorAll('.composer--upload_form--item input'); - if (inputs.length == media.size && firstWithoutDescription !== -1) { - inputs[firstWithoutDescription].focus(); - } - } - onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null); + const firstWithoutDescription = media.find(item => !item.get('description')); + onMediaDescriptionConfirm(this.context.router ? this.context.router.history : null, firstWithoutDescription.get('id')); } else if (onSubmit) { onSubmit(this.context.router ? this.context.router.history : null); } @@ -298,6 +294,8 @@ class ComposeForm extends ImmutablePureComponent { let disabledButton = isSubmitting || isUploading || isChangingUpload || (!text.trim().length && !anyMedia); + const countText = `${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`; + return ( <div className='composer'> <WarningContainer /> @@ -347,19 +345,24 @@ class ComposeForm extends ImmutablePureComponent { </div> </AutosuggestTextarea> - <OptionsContainer - advancedOptions={advancedOptions} - disabled={isSubmitting} - onChangeVisibility={onChangeVisibility} - onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness} - onUpload={onPaste} - privacy={privacy} - sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)} - spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} - /> + <div className='composer--options-wrapper'> + <OptionsContainer + advancedOptions={advancedOptions} + disabled={isSubmitting} + onChangeVisibility={onChangeVisibility} + onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness} + onUpload={onPaste} + privacy={privacy} + sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)} + spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} + /> + <div className='compose--counter-wrapper'> + <CharacterCounter text={countText} max={maxChars} /> + </div> + </div> <Publisher - countText={`${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`} + countText={countText} disabled={disabledButton} onSecondarySubmit={handleSecondarySubmit} onSubmit={handleSubmit} diff --git a/app/javascript/flavours/glitch/features/compose/components/publisher.js b/app/javascript/flavours/glitch/features/compose/components/publisher.js index e283b32b9..f5eafc6fd 100644 --- a/app/javascript/flavours/glitch/features/compose/components/publisher.js +++ b/app/javascript/flavours/glitch/features/compose/components/publisher.js @@ -49,7 +49,6 @@ class Publisher extends ImmutablePureComponent { return ( <div className={computedClass}> - <span className='count'>{diff}</span> {sideArm && sideArm !== 'none' ? ( <Button className='side_arm' diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js index 84edf664e..f89145a52 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload.js @@ -4,18 +4,12 @@ import PropTypes from 'prop-types'; import Motion from 'flavours/glitch/util/optional_motion'; import spring from 'react-motion/lib/spring'; import ImmutablePureComponent from 'react-immutable-pure-component'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; +import { FormattedMessage } from 'react-intl'; import classNames from 'classnames'; import Icon from 'flavours/glitch/components/icon'; import { isUserTouching } from 'flavours/glitch/util/is_mobile'; -const messages = defineMessages({ - description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' }, -}); - -// The component. -export default @injectIntl -class Upload extends ImmutablePureComponent { +export default class Upload extends ImmutablePureComponent { static contextTypes = { router: PropTypes.object, @@ -23,30 +17,10 @@ class Upload extends ImmutablePureComponent { static propTypes = { media: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, onUndo: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, onOpenFocalPoint: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - }; - - state = { - hovered: false, - focused: false, - dirtyDescription: null, }; - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit = () => { - this.handleInputBlur(); - this.props.onSubmit(this.context.router.history); - } - handleUndoClick = e => { e.stopPropagation(); this.props.onUndo(this.props.media.get('id')); @@ -57,69 +31,21 @@ class Upload extends ImmutablePureComponent { this.props.onOpenFocalPoint(this.props.media.get('id')); } - handleInputChange = e => { - this.setState({ dirtyDescription: e.target.value }); - } - - handleMouseEnter = () => { - this.setState({ hovered: true }); - } - - handleMouseLeave = () => { - this.setState({ hovered: false }); - } - - handleInputFocus = () => { - this.setState({ focused: true }); - } - - handleClick = () => { - this.setState({ focused: true }); - } - - handleInputBlur = () => { - const { dirtyDescription } = this.state; - - this.setState({ focused: false, dirtyDescription: null }); - - if (dirtyDescription !== null) { - this.props.onDescriptionChange(this.props.media.get('id'), dirtyDescription); - } - } - render () { const { intl, media } = this.props; - const active = this.state.hovered || this.state.focused || isUserTouching(); - const description = this.state.dirtyDescription || (this.state.dirtyDescription !== '' && media.get('description')) || ''; - const computedClass = classNames('composer--upload_form--item', { active }); const focusX = media.getIn(['meta', 'focus', 'x']); const focusY = media.getIn(['meta', 'focus', 'y']); const x = ((focusX / 2) + .5) * 100; const y = ((focusY / -2) + .5) * 100; return ( - <div className={computedClass} tabIndex='0' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave} onClick={this.handleClick} role='button'> + <div className='composer--upload_form--item' tabIndex='0' role='button'> <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12, }) }}> {({ scale }) => ( <div style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})`, backgroundPosition: `${x}% ${y}%` }}> - <div className={classNames('composer--upload_form--actions', { active })}> + <div className={classNames('composer--upload_form--actions', { active: true })}> <button className='icon-button' onClick={this.handleUndoClick}><Icon icon='times' /> <FormattedMessage id='upload_form.undo' defaultMessage='Delete' /></button> - {media.get('type') === 'image' && <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='crosshairs' /> <FormattedMessage id='upload_form.focus' defaultMessage='Crop' /></button>} - </div> - - <div className={classNames('composer--upload_form--description', { active })}> - <label> - <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span> - <textarea - placeholder={intl.formatMessage(messages.description)} - value={description} - maxLength={420} - onFocus={this.handleInputFocus} - onChange={this.handleInputChange} - onBlur={this.handleInputBlur} - onKeyDown={this.handleKeyDown} - /> - </label> + <button className='icon-button' onClick={this.handleFocalPointClick}><Icon id='pencil' /> <FormattedMessage id='upload_form.edit' defaultMessage='Edit' /></button> </div> </div> )} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_form.js b/app/javascript/flavours/glitch/features/compose/components/upload_form.js index 35880ddcc..43039c674 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload_form.js @@ -4,6 +4,7 @@ import UploadProgressContainer from '../containers/upload_progress_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; import UploadContainer from '../containers/upload_container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; +import { FormattedMessage } from 'react-intl'; export default class UploadForm extends ImmutablePureComponent { static propTypes = { @@ -15,7 +16,7 @@ export default class UploadForm extends ImmutablePureComponent { return ( <div className='composer--upload_form'> - <UploadProgressContainer /> + <UploadProgressContainer icon='upload' message={<FormattedMessage id='upload_progress.label' defaultMessage='Uploading…' />} /> {mediaIds.size > 0 && ( <div className='content'> diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js index 264c563f2..b00612983 100644 --- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js +++ b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js @@ -2,7 +2,6 @@ import React from 'react'; import PropTypes from 'prop-types'; import Motion from 'flavours/glitch/util/optional_motion'; import spring from 'react-motion/lib/spring'; -import { FormattedMessage } from 'react-intl'; import Icon from 'flavours/glitch/components/icon'; export default class UploadProgress extends React.PureComponent { @@ -10,10 +9,12 @@ export default class UploadProgress extends React.PureComponent { static propTypes = { active: PropTypes.bool, progress: PropTypes.number, + icon: PropTypes.string.isRequired, + message: PropTypes.node.isRequired, }; render () { - const { active, progress } = this.props; + const { active, progress, icon, message } = this.props; if (!active) { return null; @@ -21,10 +22,10 @@ export default class UploadProgress extends React.PureComponent { return ( <div className='composer--upload_form--progress'> - <Icon icon='upload' /> + <Icon icon={icon} /> <div className='message'> - <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' /> + {message} <div className='backdrop'> <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}> diff --git a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js index 199d43913..18e2b2f39 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js @@ -25,6 +25,8 @@ const messages = defineMessages({ defaultMessage: 'At least one media attachment is lacking a description. Consider describing all media attachments for the visually impaired before sending your toot.' }, missingDescriptionConfirm: { id: 'confirmations.missing_media_description.confirm', defaultMessage: 'Send anyway' }, + missingDescriptionEdit: { id: 'confirmations.missing_media_description.edit', + defaultMessage: 'Edit media' }, }); // State mapping. @@ -112,11 +114,13 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ dispatch(changeComposeVisibility(value)); }, - onMediaDescriptionConfirm(routerHistory) { + onMediaDescriptionConfirm(routerHistory, mediaId) { dispatch(openModal('CONFIRM', { message: intl.formatMessage(messages.missingDescriptionMessage), confirm: intl.formatMessage(messages.missingDescriptionConfirm), onConfirm: () => dispatch(submitCompose(routerHistory)), + secondary: intl.formatMessage(messages.missingDescriptionEdit), + onSecondary: () => dispatch(openModal('FOCAL_POINT', { id: mediaId })), onDoNotAsk: () => dispatch(changeLocalSetting(['confirm_missing_media_description'], false)), })); }, diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js index d6bff63ac..f687fae99 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import Upload from '../components/upload'; -import { undoUploadCompose, changeUploadCompose } from 'flavours/glitch/actions/compose'; +import { undoUploadCompose } from 'flavours/glitch/actions/compose'; import { openModal } from 'flavours/glitch/actions/modal'; import { submitCompose } from 'flavours/glitch/actions/compose'; @@ -14,10 +14,6 @@ const mapDispatchToProps = dispatch => ({ dispatch(undoUploadCompose(id)); }, - onDescriptionChange: (id, description) => { - dispatch(changeUploadCompose(id, { description })); - }, - onOpenFocalPoint: id => { dispatch(openModal('FOCAL_POINT', { id })); }, |