diff options
Diffstat (limited to 'app/javascript/mastodon/features/compose')
6 files changed, 161 insertions, 10 deletions
diff --git a/app/javascript/mastodon/features/compose/components/compose_form.js b/app/javascript/mastodon/features/compose/components/compose_form.js index 7d175a912..5b06cef7c 100644 --- a/app/javascript/mastodon/features/compose/components/compose_form.js +++ b/app/javascript/mastodon/features/compose/components/compose_form.js @@ -6,10 +6,12 @@ import PropTypes from 'prop-types'; import ReplyIndicatorContainer from '../containers/reply_indicator_container'; import AutosuggestTextarea from '../../../components/autosuggest_textarea'; import UploadButtonContainer from '../containers/upload_button_container'; +import DoodleButtonContainer from '../containers/doodle_button_container'; import { defineMessages, injectIntl } from 'react-intl'; import Collapsable from '../../../components/collapsable'; import SpoilerButtonContainer from '../containers/spoiler_button_container'; import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; +import ComposeAdvancedOptionsContainer from '../../../../glitch/components/compose/advanced_options/container'; import SensitiveButtonContainer from '../containers/sensitive_button_container'; import EmojiPickerDropdown from '../containers/emoji_picker_dropdown_container'; import UploadFormContainer from '../containers/upload_form_container'; @@ -36,6 +38,9 @@ export default class ComposeForm extends ImmutablePureComponent { suggestions: ImmutablePropTypes.list, spoiler: PropTypes.bool, privacy: PropTypes.string, + advanced_options: ImmutablePropTypes.contains({ + do_not_federate: PropTypes.bool, + }), spoiler_text: PropTypes.string, focusDate: PropTypes.instanceOf(Date), preselectDate: PropTypes.instanceOf(Date), @@ -46,11 +51,14 @@ export default class ComposeForm extends ImmutablePureComponent { onSubmit: PropTypes.func.isRequired, onClearSuggestions: PropTypes.func.isRequired, onFetchSuggestions: PropTypes.func.isRequired, + onPrivacyChange: PropTypes.func.isRequired, onSuggestionSelected: PropTypes.func.isRequired, onChangeSpoilerText: PropTypes.func.isRequired, onPaste: PropTypes.func.isRequired, onPickEmoji: PropTypes.func.isRequired, showSearch: PropTypes.bool, + settings : ImmutablePropTypes.map.isRequired, + filesAttached : PropTypes.bool, }; static defaultProps = { @@ -67,6 +75,11 @@ export default class ComposeForm extends ImmutablePureComponent { } } + handleSubmit2 = () => { + this.props.onPrivacyChange(this.props.settings.get('side_arm')); + this.handleSubmit(); + } + handleSubmit = () => { if (this.props.text !== this.autosuggestTextarea.textarea.value) { // Something changed the text inside the textarea (e.g. browser extensions like Grammarly) @@ -143,18 +156,59 @@ export default class ComposeForm extends ImmutablePureComponent { } render () { - const { intl, onPaste, showSearch } = this.props; + const { intl, onPaste, showSearch, filesAttached } = this.props; const disabled = this.props.is_submitting; - const text = [this.props.spoiler_text, countableText(this.props.text)].join(''); + const maybeEye = (this.props.advanced_options && this.props.advanced_options.do_not_federate) ? ' 👁️' : ''; + const text = [this.props.spoiler_text, countableText(this.props.text), maybeEye].join(''); + + const secondaryVisibility = this.props.settings.get('side_arm'); + const isWideView = this.props.settings.get('stretch'); + let showSideArm = secondaryVisibility !== 'none'; let publishText = ''; + let publishText2 = ''; + + const privacyIcons = { + none: '', + public: 'globe', + unlisted: 'unlock-alt', + private: 'lock', + direct: 'envelope', + }; - if (this.props.privacy === 'private' || this.props.privacy === 'direct') { - publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; + if (showSideArm) { + publishText = ( + <span> + { + <i + className={`fa fa-${privacyIcons[this.props.privacy]}`} + style={{ + paddingRight: (filesAttached || !isWideView) ? '0' : '5px', + }} + /> + }{ + (filesAttached || !isWideView) ? '' : + intl.formatMessage(messages.publish) + } + </span> + ); + + publishText2 = ( + <i + className={`fa fa-${privacyIcons[secondaryVisibility]}`} + aria-label={`${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`} + /> + ); } else { - publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); + if (this.props.privacy === 'private' || this.props.privacy === 'direct') { + publishText = <span className='compose-form__publish-private'><i className='fa fa-lock' /> {intl.formatMessage(messages.publish)}</span>; + } else { + publishText = this.props.privacy !== 'unlisted' ? intl.formatMessage(messages.publishLoud, { publish: intl.formatMessage(messages.publish) }) : intl.formatMessage(messages.publish); + } } + const submitDisabled = disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0); + return ( <div className='compose-form'> <Collapsable isVisible={this.props.spoiler} fullHeight={50}> @@ -196,14 +250,33 @@ export default class ComposeForm extends ImmutablePureComponent { <div className='compose-form__buttons-wrapper'> <div className='compose-form__buttons'> <UploadButtonContainer /> + <DoodleButtonContainer /> <PrivacyDropdownContainer /> + <ComposeAdvancedOptionsContainer /> <SensitiveButtonContainer /> <SpoilerButtonContainer /> </div> <div className='compose-form__publish'> <div className='character-counter__wrapper'><CharacterCounter max={500} text={text} /></div> - <div className='compose-form__publish-button-wrapper'><Button text={publishText} onClick={this.handleSubmit} disabled={disabled || this.props.is_uploading || length(text) > 500 || (text.length !== 0 && text.trim().length === 0)} block /></div> + <div className='compose-form__publish-button-wrapper'> + { + showSideArm ? + <Button + className='compose-form__publish__side-arm' + text={publishText2} + onClick={this.handleSubmit2} + disabled={submitDisabled} + /> : '' + } + <Button + className='compose-form__publish__primary' + text={publishText} + onClick={this.handleSubmit} + disabled={submitDisabled} + block + /> + </div> </div> </div> </div> diff --git a/app/javascript/mastodon/features/compose/components/doodle_button.js b/app/javascript/mastodon/features/compose/components/doodle_button.js new file mode 100644 index 000000000..0af02458f --- /dev/null +++ b/app/javascript/mastodon/features/compose/components/doodle_button.js @@ -0,0 +1,41 @@ +import React from 'react'; +import IconButton from '../../../components/icon_button'; +import PropTypes from 'prop-types'; +import { defineMessages, injectIntl } from 'react-intl'; +import ImmutablePureComponent from 'react-immutable-pure-component'; + +const messages = defineMessages({ + doodle: { id: 'doodle_button.label', defaultMessage: 'Add a drawing' }, +}); + +const iconStyle = { + height: null, + lineHeight: '27px', +}; + +@injectIntl +export default class UploadButton extends ImmutablePureComponent { + + static propTypes = { + disabled: PropTypes.bool, + onOpenCanvas: PropTypes.func.isRequired, + style: PropTypes.object, + intl: PropTypes.object.isRequired, + }; + + handleClick = () => { + this.props.onOpenCanvas(); + } + + render () { + + const { intl, disabled } = this.props; + + return ( + <div className='compose-form__upload-button'> + <IconButton icon='pencil' title={intl.formatMessage(messages.doodle)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} /> + </div> + ); + } + +} diff --git a/app/javascript/mastodon/features/compose/components/search_results.js b/app/javascript/mastodon/features/compose/components/search_results.js index 8350d20a5..a3e68643f 100644 --- a/app/javascript/mastodon/features/compose/components/search_results.js +++ b/app/javascript/mastodon/features/compose/components/search_results.js @@ -2,7 +2,7 @@ import React from 'react'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { FormattedMessage } from 'react-intl'; import AccountContainer from '../../../containers/account_container'; -import StatusContainer from '../../../containers/status_container'; +import StatusContainer from '../../../../glitch/components/status/container'; import { Link } from 'react-router-dom'; import ImmutablePureComponent from 'react-immutable-pure-component'; diff --git a/app/javascript/mastodon/features/compose/containers/compose_form_container.js b/app/javascript/mastodon/features/compose/containers/compose_form_container.js index 12d435ded..ffa0a3442 100644 --- a/app/javascript/mastodon/features/compose/containers/compose_form_container.js +++ b/app/javascript/mastodon/features/compose/containers/compose_form_container.js @@ -1,6 +1,6 @@ import { connect } from 'react-redux'; import ComposeForm from '../components/compose_form'; -import { uploadCompose } from '../../../actions/compose'; +import { changeComposeVisibility, uploadCompose } from '../../../actions/compose'; import { changeCompose, submitCompose, @@ -15,6 +15,7 @@ const mapStateToProps = state => ({ text: state.getIn(['compose', 'text']), suggestion_token: state.getIn(['compose', 'suggestion_token']), suggestions: state.getIn(['compose', 'suggestions']), + advanced_options: state.getIn(['compose', 'advanced_options']), spoiler: state.getIn(['compose', 'spoiler']), spoiler_text: state.getIn(['compose', 'spoiler_text']), privacy: state.getIn(['compose', 'privacy']), @@ -24,6 +25,8 @@ const mapStateToProps = state => ({ is_uploading: state.getIn(['compose', 'is_uploading']), me: state.getIn(['compose', 'me']), showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), + settings: state.get('local_settings'), + filesAttached: state.getIn(['compose', 'media_attachments']).size > 0, }); const mapDispatchToProps = (dispatch) => ({ @@ -32,6 +35,10 @@ const mapDispatchToProps = (dispatch) => ({ dispatch(changeCompose(text)); }, + onPrivacyChange (value) { + dispatch(changeComposeVisibility(value)); + }, + onSubmit () { dispatch(submitCompose()); }, diff --git a/app/javascript/mastodon/features/compose/containers/doodle_button_container.js b/app/javascript/mastodon/features/compose/containers/doodle_button_container.js new file mode 100644 index 000000000..5ada4514f --- /dev/null +++ b/app/javascript/mastodon/features/compose/containers/doodle_button_container.js @@ -0,0 +1,15 @@ +import { connect } from 'react-redux'; +import DoodleButton from '../components/doodle_button'; +import { openModal } from '../../../actions/modal'; + +const mapStateToProps = state => ({ + disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 3 || state.getIn(['compose', 'media_attachments']).some(m => m.get('type') === 'video')), +}); + +const mapDispatchToProps = dispatch => ({ + onOpenCanvas () { + dispatch(openModal('DOODLE', { noEsc: true })); + }, +}); + +export default connect(mapStateToProps, mapDispatchToProps)(DoodleButton); diff --git a/app/javascript/mastodon/features/compose/index.js b/app/javascript/mastodon/features/compose/index.js index 0c66585c9..41a97d550 100644 --- a/app/javascript/mastodon/features/compose/index.js +++ b/app/javascript/mastodon/features/compose/index.js @@ -5,6 +5,8 @@ import PropTypes from 'prop-types'; import ImmutablePropTypes from 'react-immutable-proptypes'; import { connect } from 'react-redux'; import { mountCompose, unmountCompose } from '../../actions/compose'; +import { openModal } from '../../actions/modal'; +import { changeLocalSetting } from '../../../glitch/actions/local_settings'; import { Link } from 'react-router-dom'; import { injectIntl, defineMessages } from 'react-intl'; import SearchContainer from './containers/search_container'; @@ -19,7 +21,7 @@ const messages = defineMessages({ notifications: { id: 'tabs_bar.notifications', defaultMessage: 'Notifications' }, public: { id: 'navigation_bar.public_timeline', defaultMessage: 'Federated timeline' }, community: { id: 'navigation_bar.community_timeline', defaultMessage: 'Local timeline' }, - preferences: { id: 'navigation_bar.preferences', defaultMessage: 'Preferences' }, + settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, }); @@ -48,6 +50,16 @@ export default class Compose extends React.PureComponent { this.props.dispatch(unmountCompose()); } + onLayoutClick = (e) => { + const layout = e.currentTarget.getAttribute('data-mastodon-layout'); + this.props.dispatch(changeLocalSetting(['layout'], layout)); + e.preventDefault(); + } + + openSettings = () => { + this.props.dispatch(openModal('SETTINGS', {})); + } + onFocus = () => { this.props.dispatch(changeComposing(true)); } @@ -78,12 +90,14 @@ export default class Compose extends React.PureComponent { {!columns.some(column => column.get('id') === 'PUBLIC') && ( <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><i role='img' className='fa fa-fw fa-globe' /></Link> )} - <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><i role='img' className='fa fa-fw fa-cog' /></a> + <a onClick={this.openSettings} role='button' tabIndex='0' className='drawer__tab' title={intl.formatMessage(messages.settings)} aria-label={intl.formatMessage(messages.settings)}><i role='img' className='fa fa-fw fa-cogs' /></a> <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><i role='img' className='fa fa-fw fa-sign-out' /></a> </nav> ); } + + return ( <div className='drawer'> {header} @@ -104,6 +118,7 @@ export default class Compose extends React.PureComponent { } </Motion> </div> + </div> ); } |