diff options
author | Eugen Rochko <eugen@zeonfederated.com> | 2017-05-03 02:04:16 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-05-03 02:04:16 +0200 |
commit | f5bf5ebb82e3af420dcd23d602b1be6cc86838e1 (patch) | |
tree | 92eef08642a038cf44ccbc6d16a884293e7a0814 /app/assets/javascripts/components/features/compose | |
parent | 26bc5915727e0a0173c03cb49f5193dd612fb888 (diff) |
Replace sprockets/browserify with Webpack (#2617)
* Replace browserify with webpack * Add react-intl-translations-manager * Do not minify in development, add offline-plugin for ServiceWorker background cache updates * Adjust tests and dependencies * Fix production deployments * Fix tests * More optimizations * Improve travis cache for npm stuff * Re-run travis * Add back support for custom.scss as before * Remove offline-plugin and babili * Fix issue with Immutable.List().unshift(...values) not working as expected * Make travis load schema instead of running all migrations in sequence * Fix missing React import in WarningContainer. Optimize rendering performance by using ImmutablePureComponent instead of React.PureComponent. ImmutablePureComponent uses Immutable.is() to compare props. Replace dynamic callback bindings in <UI /> * Add react definitions to places that use JSX * Add Procfile.dev for running rails, webpack and streaming API at the same time
Diffstat (limited to 'app/assets/javascripts/components/features/compose')
30 files changed, 0 insertions, 1379 deletions
diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx deleted file mode 100644 index bf6a15e5d..000000000 --- a/app/assets/javascripts/components/features/compose/components/autosuggest_account.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import Avatar from '../../../components/avatar'; -import DisplayName from '../../../components/display_name'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const AutosuggestAccount = ({ account }) => ( - <div className='autosuggest-account'> - <div className='autosuggest-account-icon'><Avatar src={account.get('avatar')} staticSrc={account.get('avatar_static')} size={18} /></div> - <DisplayName account={account} /> - </div> -); - -AutosuggestAccount.propTypes = { - account: ImmutablePropTypes.map.isRequired -}; - -export default AutosuggestAccount; diff --git a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx b/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx deleted file mode 100644 index 275b3d5a6..000000000 --- a/app/assets/javascripts/components/features/compose/components/autosuggest_status.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { FormattedMessage } from 'react-intl'; -import DisplayName from '../../../components/display_name'; -import ImmutablePropTypes from 'react-immutable-proptypes'; - -const AutosuggestStatus = ({ status }) => ( - <div className='autosuggest-status'> - <FormattedMessage id='search.status_by' defaultMessage='Status by {name}' values={{ name: <strong>@{status.getIn(['account', 'acct'])}</strong> }} /> - </div> -); - -AutosuggestStatus.propTypes = { - status: ImmutablePropTypes.map.isRequired -}; - -export default AutosuggestStatus; diff --git a/app/assets/javascripts/components/features/compose/components/character_counter.jsx b/app/assets/javascripts/components/features/compose/components/character_counter.jsx deleted file mode 100644 index 08d2ac4d1..000000000 --- a/app/assets/javascripts/components/features/compose/components/character_counter.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import PropTypes from 'prop-types'; -import { length } from 'stringz'; - -class CharacterCounter extends React.PureComponent { - - 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); - } - -} - -CharacterCounter.propTypes = { - text: PropTypes.string.isRequired, - max: PropTypes.number.isRequired -} - -export default CharacterCounter; diff --git a/app/assets/javascripts/components/features/compose/components/compose_form.jsx b/app/assets/javascripts/components/features/compose/components/compose_form.jsx deleted file mode 100644 index 6bc811160..000000000 --- a/app/assets/javascripts/components/features/compose/components/compose_form.jsx +++ /dev/null @@ -1,209 +0,0 @@ -import CharacterCounter from './character_counter'; -import Button from '../../../components/button'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ReplyIndicatorContainer from '../containers/reply_indicator_container'; -import AutosuggestTextarea from '../../../components/autosuggest_textarea'; -import { debounce } from 'react-decoration'; -import UploadButtonContainer from '../containers/upload_button_container'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Toggle from 'react-toggle'; -import Collapsable from '../../../components/collapsable'; -import SpoilerButtonContainer from '../containers/spoiler_button_container'; -import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; -import SensitiveButtonContainer from '../containers/sensitive_button_container'; -import EmojiPickerDropdown from './emoji_picker_dropdown'; -import UploadFormContainer from '../containers/upload_form_container'; -import TextIconButton from './text_icon_button'; -import WarningContainer from '../containers/warning_container'; - -const messages = defineMessages({ - placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, - spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Content warning' }, - publish: { id: 'compose_form.publish', defaultMessage: 'Toot' } -}); - -class ComposeForm extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleSubmit = this.handleSubmit.bind(this); - this.onSuggestionsClearRequested = this.onSuggestionsClearRequested.bind(this); - this.onSuggestionsFetchRequested = this.onSuggestionsFetchRequested.bind(this); - this.onSuggestionSelected = this.onSuggestionSelected.bind(this); - this.handleChangeSpoilerText = this.handleChangeSpoilerText.bind(this); - this.setAutosuggestTextarea = this.setAutosuggestTextarea.bind(this); - this.handleEmojiPick = this.handleEmojiPick.bind(this); - } - - handleChange (e) { - this.props.onChange(e.target.value); - } - - handleKeyDown (e) { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - handleSubmit () { - this.autosuggestTextarea.reset(); - this.props.onSubmit(); - } - - onSuggestionsClearRequested () { - this.props.onClearSuggestions(); - } - - @debounce(500) - onSuggestionsFetchRequested (token) { - this.props.onFetchSuggestions(token); - } - - onSuggestionSelected (tokenStart, token, value) { - this._restoreCaret = null; - this.props.onSuggestionSelected(tokenStart, token, value); - } - - handleChangeSpoilerText (e) { - this.props.onChangeSpoilerText(e.target.value); - } - - componentWillReceiveProps (nextProps) { - // If this is the update where we've finished uploading, - // save the last caret position so we can restore it below! - if (!nextProps.is_uploading && this.props.is_uploading) { - this._restoreCaret = this.autosuggestTextarea.textarea.selectionStart; - } - } - - componentDidUpdate (prevProps) { - // 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; - // this provides a convenient shortcut to drop everyone else from the conversation. - // - If we've just finished uploading an image, and have a saved caret position, - // restores the cursor to that position after the text changes! - if (this.props.focusDate !== prevProps.focusDate || (prevProps.is_uploading && !this.props.is_uploading && typeof this._restoreCaret === 'number')) { - let selectionEnd, selectionStart; - - if (this.props.preselectDate !== prevProps.preselectDate) { - selectionEnd = this.props.text.length; - selectionStart = this.props.text.search(/\s/) + 1; - } else if (typeof this._restoreCaret === 'number') { - selectionStart = this._restoreCaret; - selectionEnd = this._restoreCaret; - } else { - selectionEnd = this.props.text.length; - selectionStart = selectionEnd; - } - - this.autosuggestTextarea.textarea.setSelectionRange(selectionStart, selectionEnd); - this.autosuggestTextarea.textarea.focus(); - } - } - - setAutosuggestTextarea (c) { - this.autosuggestTextarea = c; - } - - handleEmojiPick (data) { - const position = this.autosuggestTextarea.textarea.selectionStart; - this._restoreCaret = position + data.shortname.length + 1; - this.props.onPickEmoji(position, data); - } - - render () { - const { intl, onPaste } = this.props; - const disabled = this.props.is_submitting; - const text = [this.props.spoiler_text, this.props.text].join(''); - - let publishText = ''; - let reply_to_other = false; - - 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 = intl.formatMessage(messages.publish) + (this.props.privacy !== 'unlisted' ? '!' : ''); - } - - return ( - <div className='compose-form'> - <Collapsable isVisible={this.props.spoiler} fullHeight={50}> - <div className="spoiler-input"> - <input placeholder={intl.formatMessage(messages.spoiler_placeholder)} value={this.props.spoiler_text} onChange={this.handleChangeSpoilerText} onKeyDown={this.handleKeyDown} type="text" className="spoiler-input__input" id='cw-spoiler-input'/> - </div> - </Collapsable> - - <WarningContainer /> - - <ReplyIndicatorContainer /> - - <div className='compose-form__autosuggest-wrapper'> - <AutosuggestTextarea - ref={this.setAutosuggestTextarea} - placeholder={intl.formatMessage(messages.placeholder)} - disabled={disabled} - value={this.props.text} - onChange={this.handleChange} - suggestions={this.props.suggestions} - onKeyDown={this.handleKeyDown} - onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} - onSuggestionsClearRequested={this.onSuggestionsClearRequested} - onSuggestionSelected={this.onSuggestionSelected} - onPaste={onPaste} - /> - - <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} /> - </div> - - <div className='compose-form__modifiers'> - <UploadFormContainer /> - </div> - - <div className='compose-form__buttons-wrapper'> - <div className='compose-form__buttons'> - <UploadButtonContainer /> - <PrivacyDropdownContainer /> - <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 || text.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "_").length > 500 || (text.length !==0 && text.trim().length === 0)} block /></div> - </div> - </div> - </div> - ); - } - -} - -ComposeForm.propTypes = { - intl: PropTypes.object.isRequired, - text: PropTypes.string.isRequired, - suggestion_token: PropTypes.string, - suggestions: ImmutablePropTypes.list, - spoiler: PropTypes.bool, - privacy: PropTypes.string, - spoiler_text: PropTypes.string, - focusDate: PropTypes.instanceOf(Date), - preselectDate: PropTypes.instanceOf(Date), - is_submitting: PropTypes.bool, - is_uploading: PropTypes.bool, - me: PropTypes.number, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClearSuggestions: PropTypes.func.isRequired, - onFetchSuggestions: PropTypes.func.isRequired, - onSuggestionSelected: PropTypes.func.isRequired, - onChangeSpoilerText: PropTypes.func.isRequired, - onPaste: PropTypes.func.isRequired, - onPickEmoji: PropTypes.func.isRequired -}; - -export default injectIntl(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx deleted file mode 100644 index bc22b074d..000000000 --- a/app/assets/javascripts/components/features/compose/components/emoji_picker_dropdown.jsx +++ /dev/null @@ -1,114 +0,0 @@ -import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown'; -import EmojiPicker from 'emojione-picker'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' }, - people: { id: 'emoji_button.people', defaultMessage: 'People' }, - nature: { id: 'emoji_button.nature', defaultMessage: 'Nature' }, - food: { id: 'emoji_button.food', defaultMessage: 'Food & Drink' }, - activity: { id: 'emoji_button.activity', defaultMessage: 'Activity' }, - travel: { id: 'emoji_button.travel', defaultMessage: 'Travel & Places' }, - objects: { id: 'emoji_button.objects', defaultMessage: 'Objects' }, - symbols: { id: 'emoji_button.symbols', defaultMessage: 'Symbols' }, - flags: { id: 'emoji_button.flags', defaultMessage: 'Flags' } -}); - -const settings = { - imageType: 'png', - sprites: false, - imagePathPNG: '/emoji/' -}; - -const dropdownStyle = { - position: 'absolute', - right: '5px', - top: '5px' -}; - -const dropdownTriggerStyle = { - display: 'block', - fontSize: '24px', - lineHeight: '24px', - marginLeft: '2px', - width: '24px' -} - -class EmojiPickerDropdown extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.setRef = this.setRef.bind(this); - this.handleChange = this.handleChange.bind(this); - } - - setRef (c) { - this.dropdown = c; - } - - handleChange (data) { - this.dropdown.hide(); - this.props.onPickEmoji(data); - } - - render () { - const { intl } = this.props; - - const categories = { - people: { - title: intl.formatMessage(messages.people), - emoji: 'smile', - }, - nature: { - title: intl.formatMessage(messages.nature), - emoji: 'hamster', - }, - food: { - title: intl.formatMessage(messages.food), - emoji: 'pizza', - }, - activity: { - title: intl.formatMessage(messages.activity), - emoji: 'soccer', - }, - travel: { - title: intl.formatMessage(messages.travel), - emoji: 'earth_americas', - }, - objects: { - title: intl.formatMessage(messages.objects), - emoji: 'bulb', - }, - symbols: { - title: intl.formatMessage(messages.symbols), - emoji: 'clock9', - }, - flags: { - title: intl.formatMessage(messages.flags), - emoji: 'flag_gb', - } - } - - return ( - <Dropdown ref={this.setRef} style={dropdownStyle}> - <DropdownTrigger className='emoji-button' title={intl.formatMessage(messages.emoji)} style={dropdownTriggerStyle}> - <img draggable="false" className="emojione" alt="🙂" src="/emoji/1f602.svg" /> - </DropdownTrigger> - - <DropdownContent className='dropdown__left'> - <EmojiPicker emojione={settings} onChange={this.handleChange} searchPlaceholder={intl.formatMessage(messages.emoji_search)} categories={categories} search={true} /> - </DropdownContent> - </Dropdown> - ); - } - -} - -EmojiPickerDropdown.propTypes = { - intl: PropTypes.object.isRequired, - onPickEmoji: PropTypes.func.isRequired -}; - -export default injectIntl(EmojiPickerDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx b/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx deleted file mode 100644 index aae0592c6..000000000 --- a/app/assets/javascripts/components/features/compose/components/navigation_bar.jsx +++ /dev/null @@ -1,32 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from '../../../components/avatar'; -import IconButton from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; -import Permalink from '../../../components/permalink'; -import { FormattedMessage } from 'react-intl'; -import { Link } from 'react-router'; - -class NavigationBar extends React.PureComponent { - - render () { - return ( - <div className='navigation-bar'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}><Avatar src={this.props.account.get('avatar')} animate size={40} /></Permalink> - - <div className='navigation-bar__profile'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> - <strong className='navigation-bar__profile-account'>@{this.props.account.get('acct')}</strong> - </Permalink> - <a href='/settings/profile' className='navigation-bar__profile-edit'><FormattedMessage id='navigation_bar.edit_profile' defaultMessage='Edit profile' /></a> - </div> - </div> - ); - } - -} - -NavigationBar.propTypes = { - account: ImmutablePropTypes.map.isRequired -}; - -export default NavigationBar; diff --git a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx b/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx deleted file mode 100644 index 82b3454c6..000000000 --- a/app/assets/javascripts/components/features/compose/components/privacy_dropdown.jsx +++ /dev/null @@ -1,104 +0,0 @@ -import PropTypes from 'prop-types'; -import { injectIntl, defineMessages } from 'react-intl'; -import IconButton from '../../../components/icon_button'; - -const messages = defineMessages({ - public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, - public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' }, - unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, - unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' }, - private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, - private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' }, - direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, - direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' }, - change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' } -}); - -const iconStyle = { - height: null, - lineHeight: '27px' -} - -class PrivacyDropdown extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.state = { - open: false - }; - this.handleToggle = this.handleToggle.bind(this); - this.handleClick = this.handleClick.bind(this); - this.onGlobalClick = this.onGlobalClick.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleToggle () { - this.setState({ open: !this.state.open }); - } - - handleClick (value, e) { - e.preventDefault(); - this.setState({ open: false }); - this.props.onChange(value); - } - - onGlobalClick (e) { - if (e.target !== this.node && !this.node.contains(e.target) && this.state.open) { - this.setState({ open: false }); - } - } - - componentDidMount () { - window.addEventListener('click', this.onGlobalClick); - window.addEventListener('touchstart', this.onGlobalClick); - } - - componentWillUnmount () { - window.removeEventListener('click', this.onGlobalClick); - window.removeEventListener('touchstart', this.onGlobalClick); - } - - setRef (c) { - this.node = c; - } - - render () { - const { value, onChange, intl } = this.props; - const { open } = this.state; - - const options = [ - { icon: 'globe', value: 'public', shortText: intl.formatMessage(messages.public_short), longText: intl.formatMessage(messages.public_long) }, - { icon: 'unlock-alt', value: 'unlisted', shortText: intl.formatMessage(messages.unlisted_short), longText: intl.formatMessage(messages.unlisted_long) }, - { icon: 'lock', value: 'private', shortText: intl.formatMessage(messages.private_short), longText: intl.formatMessage(messages.private_long) }, - { icon: 'envelope', value: 'direct', shortText: intl.formatMessage(messages.direct_short), longText: intl.formatMessage(messages.direct_long) } - ]; - - const valueOption = options.find(item => item.value === value); - - return ( - <div ref={this.setRef} className={`privacy-dropdown ${open ? 'active' : ''}`}> - <div className='privacy-dropdown__value'><IconButton className='privacy-dropdown__value-icon' icon={valueOption.icon} title={intl.formatMessage(messages.change_privacy)} size={18} active={open} inverted onClick={this.handleToggle} style={iconStyle}/></div> - <div className='privacy-dropdown__dropdown'> - {options.map(item => - <div role='button' tabIndex='0' key={item.value} onClick={this.handleClick.bind(this, item.value)} className={`privacy-dropdown__option ${item.value === value ? 'active' : ''}`}> - <div className='privacy-dropdown__option__icon'><i className={`fa fa-fw fa-${item.icon}`} /></div> - <div className='privacy-dropdown__option__content'> - <strong>{item.shortText}</strong> - {item.longText} - </div> - </div> - )} - </div> - </div> - ); - } - -} - -PrivacyDropdown.propTypes = { - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(PrivacyDropdown); diff --git a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx b/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx deleted file mode 100644 index 442ed5a35..000000000 --- a/app/assets/javascripts/components/features/compose/components/reply_indicator.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Avatar from '../../../components/avatar'; -import IconButton from '../../../components/icon_button'; -import DisplayName from '../../../components/display_name'; -import emojify from '../../../emoji'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' } -}); - -class ReplyIndicator extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - this.handleAccountClick = this.handleAccountClick.bind(this); - } - - handleClick () { - this.props.onCancel(); - } - - handleAccountClick (e) { - if (e.button === 0) { - e.preventDefault(); - this.context.router.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - } - - render () { - const { status, intl } = this.props; - - if (!status) { - return null; - } - - const content = { __html: emojify(status.get('content')) }; - - return ( - <div className='reply-indicator'> - <div className='reply-indicator__header'> - <div className='reply-indicator__cancel'><IconButton title={intl.formatMessage(messages.cancel)} icon='times' onClick={this.handleClick} /></div> - - <a href={status.getIn(['account', 'url'])} onClick={this.handleAccountClick} className='reply-indicator__display-name'> - <div className='reply-indicator__display-avatar'><Avatar size={24} src={status.getIn(['account', 'avatar'])} staticSrc={status.getIn(['account', 'avatar_static'])} /></div> - <DisplayName account={status.get('account')} /> - </a> - </div> - - <div className='reply-indicator__content' dangerouslySetInnerHTML={content} /> - </div> - ); - } - -} - -ReplyIndicator.contextTypes = { - router: PropTypes.object -}; - -ReplyIndicator.propTypes = { - status: ImmutablePropTypes.map, - onCancel: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(ReplyIndicator); diff --git a/app/assets/javascripts/components/features/compose/components/search.jsx b/app/assets/javascripts/components/features/compose/components/search.jsx deleted file mode 100644 index f62248a33..000000000 --- a/app/assets/javascripts/components/features/compose/components/search.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' } -}); - -class Search extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleKeyDown = this.handleKeyDown.bind(this); - this.handleFocus = this.handleFocus.bind(this); - this.handleClear = this.handleClear.bind(this); - } - - handleChange (e) { - this.props.onChange(e.target.value); - } - - handleClear (e) { - e.preventDefault(); - - if (this.props.value.length > 0 || this.props.submitted) { - this.props.onClear(); - } - } - - handleKeyDown (e) { - if (e.key === 'Enter') { - e.preventDefault(); - this.props.onSubmit(); - } - } - - noop () { - - } - - handleFocus () { - this.props.onShow(); - } - - render () { - const { intl, value, submitted } = this.props; - const hasValue = value.length > 0 || submitted; - - return ( - <div className='search'> - <input - className='search__input' - type='text' - placeholder={intl.formatMessage(messages.placeholder)} - value={value} - onChange={this.handleChange} - onKeyUp={this.handleKeyDown} - onFocus={this.handleFocus} - /> - - <div role='button' tabIndex='0' className='search__icon' onClick={this.handleClear}> - <i className={`fa fa-search ${hasValue ? '' : 'active'}`} /> - <i aria-label={intl.formatMessage(messages.placeholder)} className={`fa fa-times-circle ${hasValue ? 'active' : ''}`} /> - </div> - </div> - ); - } - -} - -Search.propTypes = { - value: PropTypes.string.isRequired, - submitted: PropTypes.bool, - onChange: PropTypes.func.isRequired, - onSubmit: PropTypes.func.isRequired, - onClear: PropTypes.func.isRequired, - onShow: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(Search); diff --git a/app/assets/javascripts/components/features/compose/components/search_results.jsx b/app/assets/javascripts/components/features/compose/components/search_results.jsx deleted file mode 100644 index 00bfd1786..000000000 --- a/app/assets/javascripts/components/features/compose/components/search_results.jsx +++ /dev/null @@ -1,65 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import AccountContainer from '../../../containers/account_container'; -import StatusContainer from '../../../containers/status_container'; -import { Link } from 'react-router'; - -class SearchResults extends React.PureComponent { - - render () { - const { results } = this.props; - - let accounts, statuses, hashtags; - let count = 0; - - if (results.get('accounts') && results.get('accounts').size > 0) { - count += results.get('accounts').size; - accounts = ( - <div className='search-results__section'> - {results.get('accounts').map(accountId => <AccountContainer key={accountId} id={accountId} />)} - </div> - ); - } - - if (results.get('statuses') && results.get('statuses').size > 0) { - count += results.get('statuses').size; - statuses = ( - <div className='search-results__section'> - {results.get('statuses').map(statusId => <StatusContainer key={statusId} id={statusId} />)} - </div> - ); - } - - if (results.get('hashtags') && results.get('hashtags').size > 0) { - count += results.get('hashtags').size; - hashtags = ( - <div className='search-results__section'> - {results.get('hashtags').map(hashtag => - <Link className='search-results__hashtag' to={`/timelines/tag/${hashtag}`}> - #{hashtag} - </Link> - )} - </div> - ); - } - - return ( - <div className='search-results'> - <div className='search-results__header'> - <FormattedMessage id='search_results.total' defaultMessage='{count, number} {count, plural, one {result} other {results}}' values={{ count }} /> - </div> - - {accounts} - {statuses} - {hashtags} - </div> - ); - } - -} - -SearchResults.propTypes = { - results: ImmutablePropTypes.map.isRequired -}; - -export default SearchResults; diff --git a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx b/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx deleted file mode 100644 index 4252596c2..000000000 --- a/app/assets/javascripts/components/features/compose/components/text_icon_button.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types'; - -class TextIconButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleClick = this.handleClick.bind(this); - } - - handleClick (e) { - e.preventDefault(); - this.props.onClick(); - } - - render () { - const { label, title, active, ariaControls } = this.props; - - return ( - <button title={title} aria-label={title} className={`text-icon-button ${active ? 'active' : ''}`} aria-expanded={active} onClick={this.handleClick} aria-controls={ariaControls}> - {label} - </button> - ); - } - -} - -TextIconButton.propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string -}; - -export default TextIconButton; diff --git a/app/assets/javascripts/components/features/compose/components/upload_button.jsx b/app/assets/javascripts/components/features/compose/components/upload_button.jsx deleted file mode 100644 index 9b2de0332..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_button.jsx +++ /dev/null @@ -1,60 +0,0 @@ -import IconButton from '../../../components/icon_button'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; - -const messages = defineMessages({ - upload: { id: 'upload_button.label', defaultMessage: 'Add media' } -}); - - -const iconStyle = { - height: null, - lineHeight: '27px' -} - -class UploadButton extends React.PureComponent { - - constructor (props, context) { - super(props, context); - this.handleChange = this.handleChange.bind(this); - this.handleClick = this.handleClick.bind(this); - this.setRef = this.setRef.bind(this); - } - - handleChange (e) { - if (e.target.files.length > 0) { - this.props.onSelectFile(e.target.files); - } - } - - handleClick () { - this.fileElement.click(); - } - - setRef (c) { - this.fileElement = c; - } - - render () { - - const { intl, resetFileKey, disabled } = this.props; - - return ( - <div className='compose-form__upload-button'> - <IconButton icon='camera' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle}/> - <input key={resetFileKey} ref={this.setRef} type='file' multiple={false} onChange={this.handleChange} disabled={disabled} style={{ display: 'none' }} /> - </div> - ); - } - -} - -UploadButton.propTypes = { - disabled: PropTypes.bool, - onSelectFile: PropTypes.func.isRequired, - style: PropTypes.object, - resetFileKey: PropTypes.number, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(UploadButton); diff --git a/app/assets/javascripts/components/features/compose/components/upload_form.jsx b/app/assets/javascripts/components/features/compose/components/upload_form.jsx deleted file mode 100644 index a2fb7cfe0..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_form.jsx +++ /dev/null @@ -1,45 +0,0 @@ -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from '../../../components/icon_button'; -import { defineMessages, injectIntl } from 'react-intl'; -import UploadProgressContainer from '../containers/upload_progress_container'; -import { Motion, spring } from 'react-motion'; - -const messages = defineMessages({ - undo: { id: 'upload_form.undo', defaultMessage: 'Undo' } -}); - -class UploadForm extends React.PureComponent { - - render () { - const { intl, media } = this.props; - - const uploads = media.map(attachment => - <div className='compose-form__upload' key={attachment.get('id')}> - <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> - {({ scale }) => - <div className='compose-form__upload-thumbnail' style={{ transform: `translateZ(0) scale(${scale})`, backgroundImage: `url(${attachment.get('preview_url')})` }}> - <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.props.onRemoveFile.bind(this, attachment.get('id'))} /> - </div> - } - </Motion> - </div> - ); - - return ( - <div className='compose-form__upload-wrapper'> - <UploadProgressContainer /> - <div className='compose-form__uploads-wrapper'>{uploads}</div> - </div> - ); - } - -} - -UploadForm.propTypes = { - media: ImmutablePropTypes.list.isRequired, - onRemoveFile: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default injectIntl(UploadForm); diff --git a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx b/app/assets/javascripts/components/features/compose/components/upload_progress.jsx deleted file mode 100644 index 8f03bb76a..000000000 --- a/app/assets/javascripts/components/features/compose/components/upload_progress.jsx +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types'; -import { Motion, spring } from 'react-motion'; -import { FormattedMessage } from 'react-intl'; - -class UploadProgress extends React.PureComponent { - - render () { - const { active, progress } = this.props; - - if (!active) { - return null; - } - - return ( - <div className='upload-progress'> - <div className='upload-progress__icon'> - <i className='fa fa-upload' /> - </div> - - <div className='upload-progress__message'> - <FormattedMessage id='upload_progress.label' defaultMessage='Uploading...' /> - - <div className='upload-progress__backdrop'> - <Motion defaultStyle={{ width: 0 }} style={{ width: spring(progress) }}> - {({ width }) => - <div className='upload-progress__tracker' style={{ width: `${width}%` }} /> - } - </Motion> - </div> - </div> - </div> - ); - } - -} - -UploadProgress.propTypes = { - active: PropTypes.bool, - progress: PropTypes.number -}; - -export default UploadProgress; diff --git a/app/assets/javascripts/components/features/compose/components/warning.jsx b/app/assets/javascripts/components/features/compose/components/warning.jsx deleted file mode 100644 index ff1989755..000000000 --- a/app/assets/javascripts/components/features/compose/components/warning.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import PropTypes from 'prop-types'; - -class Warning extends React.PureComponent { - - constructor (props) { - super(props); - } - - render () { - const { message } = this.props; - - return ( - <div className='compose-form__warning'> - {message} - </div> - ); - } - -} - -Warning.propTypes = { - message: PropTypes.node.isRequired -}; - -export default Warning; diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx deleted file mode 100644 index de76a364d..000000000 --- a/app/assets/javascripts/components/features/compose/containers/autosuggest_account_container.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestAccount from '../components/autosuggest_account'; -import { makeGetAccount } from '../../../selectors'; - -const makeMapStateToProps = () => { - const getAccount = makeGetAccount(); - - const mapStateToProps = (state, { id }) => ({ - account: getAccount(state, id) - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(AutosuggestAccount); diff --git a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx b/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx deleted file mode 100644 index ef46eb09c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/autosuggest_status_container.jsx +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestStatus from '../components/autosuggest_status'; -import { makeGetStatus } from '../../../selectors'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, { id }) => ({ - status: getStatus(state, id) - }); - - return mapStateToProps; -}; - -export default connect(makeMapStateToProps)(AutosuggestStatus); diff --git a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx deleted file mode 100644 index 892183b83..000000000 --- a/app/assets/javascripts/components/features/compose/containers/compose_form_container.jsx +++ /dev/null @@ -1,64 +0,0 @@ -import { connect } from 'react-redux'; -import ComposeForm from '../components/compose_form'; -import { uploadCompose } from '../../../actions/compose'; -import { - changeCompose, - submitCompose, - clearComposeSuggestions, - fetchComposeSuggestions, - selectComposeSuggestion, - changeComposeSpoilerText, - insertEmojiCompose -} from '../../../actions/compose'; - -const mapStateToProps = state => ({ - text: state.getIn(['compose', 'text']), - suggestion_token: state.getIn(['compose', 'suggestion_token']), - suggestions: state.getIn(['compose', 'suggestions']), - spoiler: state.getIn(['compose', 'spoiler']), - spoiler_text: state.getIn(['compose', 'spoiler_text']), - privacy: state.getIn(['compose', 'privacy']), - focusDate: state.getIn(['compose', 'focusDate']), - preselectDate: state.getIn(['compose', 'preselectDate']), - is_submitting: state.getIn(['compose', 'is_submitting']), - is_uploading: state.getIn(['compose', 'is_uploading']), - me: state.getIn(['compose', 'me']) -}); - -const mapDispatchToProps = (dispatch) => ({ - - onChange (text) { - dispatch(changeCompose(text)); - }, - - onSubmit () { - dispatch(submitCompose()); - }, - - onClearSuggestions () { - dispatch(clearComposeSuggestions()); - }, - - onFetchSuggestions (token) { - dispatch(fetchComposeSuggestions(token)); - }, - - onSuggestionSelected (position, token, accountId) { - dispatch(selectComposeSuggestion(position, token, accountId)); - }, - - onChangeSpoilerText (checked) { - dispatch(changeComposeSpoilerText(checked)); - }, - - onPaste (files) { - dispatch(uploadCompose(files)); - }, - - onPickEmoji (position, data) { - dispatch(insertEmojiCompose(position, data)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); diff --git a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx b/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx deleted file mode 100644 index 0006608da..000000000 --- a/app/assets/javascripts/components/features/compose/containers/navigation_container.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import { connect } from 'react-redux'; -import NavigationBar from '../components/navigation_bar'; - -const mapStateToProps = (state, props) => { - return { - account: state.getIn(['accounts', state.getIn(['meta', 'me'])]) - }; -}; - -export default connect(mapStateToProps)(NavigationBar); diff --git a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx b/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx deleted file mode 100644 index 1eee8f84c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/privacy_dropdown_container.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import PrivacyDropdown from '../components/privacy_dropdown'; -import { changeComposeVisibility } from '../../../actions/compose'; - -const mapStateToProps = state => ({ - value: state.getIn(['compose', 'privacy']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeVisibility(value)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx b/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx deleted file mode 100644 index 39b48f3b6..000000000 --- a/app/assets/javascripts/components/features/compose/containers/reply_indicator_container.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import { cancelReplyCompose } from '../../../actions/compose'; -import { makeGetStatus } from '../../../selectors'; -import ReplyIndicator from '../components/reply_indicator'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = (state, props) => ({ - status: getStatus(state, state.getIn(['compose', 'in_reply_to'])), - }); - - return mapStateToProps; -}; - -const mapDispatchToProps = dispatch => ({ - - onCancel () { - dispatch(cancelReplyCompose()); - } - -}); - -export default connect(makeMapStateToProps, mapDispatchToProps)(ReplyIndicator); diff --git a/app/assets/javascripts/components/features/compose/containers/search_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_container.jsx deleted file mode 100644 index 906c0c28c..000000000 --- a/app/assets/javascripts/components/features/compose/containers/search_container.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux'; -import { - changeSearch, - clearSearch, - submitSearch, - showSearch -} from '../../../actions/search'; -import Search from '../components/search'; - -const mapStateToProps = state => ({ - value: state.getIn(['search', 'value']), - submitted: state.getIn(['search', 'submitted']) -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeSearch(value)); - }, - - onClear () { - dispatch(clearSearch()); - }, - - onSubmit () { - dispatch(submitSearch()); - }, - - onShow () { - dispatch(showSearch()); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Search); diff --git a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx b/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx deleted file mode 100644 index e5911fd38..000000000 --- a/app/assets/javascripts/components/features/compose/containers/search_results_container.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import SearchResults from '../components/search_results'; - -const mapStateToProps = state => ({ - results: state.getIn(['search', 'results']) -}); - -export default connect(mapStateToProps)(SearchResults); diff --git a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx deleted file mode 100644 index c83598a7d..000000000 --- a/app/assets/javascripts/components/features/compose/containers/sensitive_button_container.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import TextIconButton from '../components/text_icon_button'; -import { changeComposeSensitivity } from '../../../actions/compose'; -import { Motion, spring } from 'react-motion'; -import { injectIntl, defineMessages } from 'react-intl'; - -const messages = defineMessages({ - title: { id: 'compose_form.sensitive', defaultMessage: 'Mark media as sensitive' } -}); - -const mapStateToProps = state => ({ - visible: state.getIn(['compose', 'media_attachments']).size > 0, - active: state.getIn(['compose', 'sensitive']) -}); - -const mapDispatchToProps = dispatch => ({ - - onClick () { - dispatch(changeComposeSensitivity()); - } - -}); - -class SensitiveButton extends React.PureComponent { - - render () { - const { visible, active, onClick, intl } = this.props; - - return ( - <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}> - {({ scale }) => - <div style={{ display: visible ? 'block' : 'none', transform: `translateZ(0) scale(${scale})` }}> - <TextIconButton onClick={onClick} label='NSFW' title={intl.formatMessage(messages.title)} active={active} /> - </div> - } - </Motion> - ); - } - -} - -SensitiveButton.propTypes = { - visible: PropTypes.bool, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton)); diff --git a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx deleted file mode 100644 index b1c80fe19..000000000 --- a/app/assets/javascripts/components/features/compose/containers/spoiler_button_container.jsx +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux'; -import TextIconButton from '../components/text_icon_button'; -import { changeComposeSpoilerness } from '../../../actions/compose'; -import { injectIntl, defineMessages } from 'react-intl'; - -const messages = defineMessages({ - title: { id: 'compose_form.spoiler', defaultMessage: 'Hide text behind warning' } -}); - -const mapStateToProps = (state, { intl }) => ({ - label: 'CW', - title: intl.formatMessage(messages.title), - active: state.getIn(['compose', 'spoiler']), - ariaControls: 'cw-spoiler-input' -}); - -const mapDispatchToProps = dispatch => ({ - - onClick () { - dispatch(changeComposeSpoilerness()); - } - -}); - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(TextIconButton)); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx deleted file mode 100644 index 78e5312f5..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_button_container.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import { connect } from 'react-redux'; -import UploadButton from '../components/upload_button'; -import { uploadCompose } from '../../../actions/compose'; - -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')), - resetFileKey: state.getIn(['compose', 'resetFileKey']) -}); - -const mapDispatchToProps = dispatch => ({ - - onSelectFile (files) { - dispatch(uploadCompose(files)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(UploadButton); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx deleted file mode 100644 index a6a202e17..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_form_container.jsx +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import UploadForm from '../components/upload_form'; -import { undoUploadCompose } from '../../../actions/compose'; - -const mapStateToProps = (state, props) => ({ - media: state.getIn(['compose', 'media_attachments']), -}); - -const mapDispatchToProps = dispatch => ({ - - onRemoveFile (media_id) { - dispatch(undoUploadCompose(media_id)); - } - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(UploadForm); diff --git a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx b/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx deleted file mode 100644 index b0f1d4d19..000000000 --- a/app/assets/javascripts/components/features/compose/containers/upload_progress_container.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import { connect } from 'react-redux'; -import UploadProgress from '../components/upload_progress'; - -const mapStateToProps = (state, props) => ({ - active: state.getIn(['compose', 'is_uploading']), - progress: state.getIn(['compose', 'progress']) -}); - -export default connect(mapStateToProps)(UploadProgress); diff --git a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx b/app/assets/javascripts/components/features/compose/containers/warning_container.jsx deleted file mode 100644 index cd744ed82..000000000 --- a/app/assets/javascripts/components/features/compose/containers/warning_container.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { connect } from 'react-redux'; -import Warning from '../components/warning'; -import { createSelector } from 'reselect'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; - -const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig)); - -const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => { - return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : []; -}); - -const mapStateToProps = state => { - const mentionedUsernames = getMentionedUsernames(state); - const mentionedUsernamesWithDomains = getMentionedDomains(state); - - return { - needsLeakWarning: (state.getIn(['compose', 'privacy']) === 'private' || state.getIn(['compose', 'privacy']) === 'direct') && mentionedUsernames !== null, - mentionedDomains: mentionedUsernamesWithDomains, - needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', state.getIn(['meta', 'me']), 'locked']) - }; -}; - -const WarningWrapper = ({ needsLeakWarning, needsLockWarning, mentionedDomains }) => { - if (needsLockWarning) { - return <Warning message={<FormattedMessage id='compose_form.lock_disclaimer' defaultMessage='Your account is not {locked}. Anyone can follow you to view your follower-only posts.' values={{ locked: <a href='/settings/profile'><FormattedMessage id='compose_form.lock_disclaimer.lock' defaultMessage='locked' /></a> }} />} />; - } else if (needsLeakWarning) { - return ( - <Warning - message={<FormattedMessage - id='compose_form.privacy_disclaimer' - defaultMessage='Your private status will be delivered to mentioned users on {domains}. Do you trust {domainsCount, plural, one {that server} other {those servers}}? Post privacy only works on Mastodon instances. If {domains} {domainsCount, plural, one {is not a Mastodon instance} other {are not Mastodon instances}}, there will be no indication that your post is private, and it may be boosted or otherwise made visible to unintended recipients.' - values={{ domains: <strong>{mentionedDomains.join(', ')}</strong>, domainsCount: mentionedDomains.length }} - />} - /> - ); - } - - return null; -}; - -WarningWrapper.propTypes = { - needsLeakWarning: PropTypes.bool, - needsLockWarning: PropTypes.bool, - mentionedDomains: PropTypes.array.isRequired, -}; - -export default connect(mapStateToProps)(WarningWrapper); diff --git a/app/assets/javascripts/components/features/compose/index.jsx b/app/assets/javascripts/components/features/compose/index.jsx deleted file mode 100644 index ae1b52ca0..000000000 --- a/app/assets/javascripts/components/features/compose/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -import ComposeFormContainer from './containers/compose_form_container'; -import UploadFormContainer from './containers/upload_form_container'; -import NavigationContainer from './containers/navigation_container'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { mountCompose, unmountCompose } from '../../actions/compose'; -import { Link } from 'react-router'; -import { injectIntl, defineMessages } from 'react-intl'; -import SearchContainer from './containers/search_container'; -import { Motion, spring } from 'react-motion'; -import SearchResultsContainer from './containers/search_results_container'; - -const messages = defineMessages({ - start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - 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' }, - logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' } -}); - -const mapStateToProps = state => ({ - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']) -}); - -class Compose extends React.PureComponent { - - componentDidMount () { - this.props.dispatch(mountCompose()); - } - - componentWillUnmount () { - this.props.dispatch(unmountCompose()); - } - - render () { - const { withHeader, showSearch, intl } = this.props; - - let header = ''; - - if (withHeader) { - header = ( - <div className='drawer__header'> - <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)}><i role="img" aria-label={intl.formatMessage(messages.start)} className='fa fa-fw fa-asterisk' /></Link> - <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)}><i role="img" aria-label={intl.formatMessage(messages.community)} className='fa fa-fw fa-users' /></Link> - <Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)}><i role="img" aria-label={intl.formatMessage(messages.public)} className='fa fa-fw fa-globe' /></Link> - <a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)}><i role="img" aria-label={intl.formatMessage(messages.preferences)} className='fa fa-fw fa-cog' /></a> - <a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)}><i role="img" aria-label={intl.formatMessage(messages.logout)} className='fa fa-fw fa-sign-out' /></a> - </div> - ); - } - - return ( - <div className='drawer'> - {header} - - <SearchContainer /> - - <div className='drawer__pager'> - <div className='drawer__inner'> - <NavigationContainer /> - <ComposeFormContainer /> - </div> - - <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> - {({ x }) => - <div className='drawer__inner darker' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> - <SearchResultsContainer /> - </div> - } - </Motion> - </div> - </div> - ); - } - -} - -Compose.propTypes = { - dispatch: PropTypes.func.isRequired, - withHeader: PropTypes.bool, - showSearch: PropTypes.bool, - intl: PropTypes.object.isRequired -}; - -export default connect(mapStateToProps)(injectIntl(Compose)); |