diff options
author | kibigo! <marrus-sh@users.noreply.github.com> | 2017-12-23 22:16:45 -0800 |
---|---|---|
committer | kibigo! <marrus-sh@users.noreply.github.com> | 2018-01-04 18:21:59 -0800 |
commit | 924ffe81d477a8cf890c8117efb94b908760bccc (patch) | |
tree | acefef7362929f4495424fbb037c3be59cca318f /app/javascript/flavours/glitch/features/compose | |
parent | fc884d015a1a2d6c31976af3d63039390fa15939 (diff) |
WIPgit status <Compose> Refactor; <Composer> ed.
Diffstat (limited to 'app/javascript/flavours/glitch/features/compose')
36 files changed, 0 insertions, 2406 deletions
diff --git a/app/javascript/flavours/glitch/features/compose/components/advanced_options.js b/app/javascript/flavours/glitch/features/compose/components/advanced_options.js deleted file mode 100644 index 045bad2e5..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/advanced_options.js +++ /dev/null @@ -1,62 +0,0 @@ -// Package imports. -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { injectIntl, defineMessages } from 'react-intl'; - -// Our imports. -import ComposeAdvancedOptionsToggle from './advanced_options_toggle'; -import ComposeDropdown from './dropdown'; - -const messages = defineMessages({ - local_only_short : - { id: 'advanced-options.local-only.short', defaultMessage: 'Local-only' }, - local_only_long : - { id: 'advanced-options.local-only.long', defaultMessage: 'Do not post to other instances' }, - advanced_options_icon_title : - { id: 'advanced_options.icon_title', defaultMessage: 'Advanced options' }, -}); - -@injectIntl -export default class ComposeAdvancedOptions extends React.PureComponent { - - static propTypes = { - values : ImmutablePropTypes.contains({ - do_not_federate : PropTypes.bool.isRequired, - }).isRequired, - onChange : PropTypes.func.isRequired, - intl : PropTypes.object.isRequired, - }; - - render () { - const { intl, values } = this.props; - const options = [ - { icon: 'wifi', shortText: messages.local_only_short, longText: messages.local_only_long, name: 'do_not_federate' }, - ]; - const anyEnabled = values.some((enabled) => enabled); - - const optionElems = options.map((option) => { - return ( - <ComposeAdvancedOptionsToggle - onChange={this.props.onChange} - active={values.get(option.name)} - key={option.name} - name={option.name} - shortText={intl.formatMessage(option.shortText)} - longText={intl.formatMessage(option.longText)} - /> - ); - }); - - return ( - <ComposeDropdown - title={intl.formatMessage(messages.advanced_options_icon_title)} - icon='home' - highlight={anyEnabled} - > - {optionElems} - </ComposeDropdown> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js b/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js deleted file mode 100644 index 98b3b6a44..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/advanced_options_toggle.js +++ /dev/null @@ -1,35 +0,0 @@ -// Package imports. -import React from 'react'; -import PropTypes from 'prop-types'; -import Toggle from 'react-toggle'; - -export default class ComposeAdvancedOptionsToggle extends React.PureComponent { - - static propTypes = { - onChange: PropTypes.func.isRequired, - active: PropTypes.bool.isRequired, - name: PropTypes.string.isRequired, - shortText: PropTypes.string.isRequired, - longText: PropTypes.string.isRequired, - } - - onToggle = () => { - this.props.onChange(this.props.name); - } - - render() { - const { active, shortText, longText } = this.props; - return ( - <div role='button' tabIndex='0' className='advanced-options-dropdown__option' onClick={this.onToggle}> - <div className='advanced-options-dropdown__option__toggle'> - <Toggle checked={active} onChange={this.onToggle} /> - </div> - <div className='advanced-options-dropdown__option__content'> - <strong>{shortText}</strong> - {longText} - </div> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/attach_options.js b/app/javascript/flavours/glitch/features/compose/components/attach_options.js deleted file mode 100644 index 6c7a1f55f..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/attach_options.js +++ /dev/null @@ -1,131 +0,0 @@ -// Package imports // -import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { injectIntl, defineMessages } from 'react-intl'; - -// Our imports // -import ComposeDropdown from './dropdown'; -import { uploadCompose } from 'flavours/glitch/actions/compose'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { openModal } from 'flavours/glitch/actions/modal'; - -const messages = defineMessages({ - upload : - { id: 'compose.attach.upload', defaultMessage: 'Upload a file' }, - doodle : - { id: 'compose.attach.doodle', defaultMessage: 'Draw something' }, - attach : - { id: 'compose.attach', defaultMessage: 'Attach...' }, -}); - -const mapStateToProps = state => ({ - // This horrible expression is copied from vanilla upload_button_container - 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']), - acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), -}); - -const mapDispatchToProps = dispatch => ({ - onSelectFile (files) { - dispatch(uploadCompose(files)); - }, - onOpenDoodle () { - dispatch(openModal('DOODLE', { noEsc: true })); - }, -}); - -@injectIntl -@connect(mapStateToProps, mapDispatchToProps) -export default class ComposeAttachOptions extends ImmutablePureComponent { - - static propTypes = { - intl : PropTypes.object.isRequired, - resetFileKey: PropTypes.number, - acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired, - disabled: PropTypes.bool, - onSelectFile: PropTypes.func.isRequired, - onOpenDoodle: PropTypes.func.isRequired, - }; - - handleItemClick = bt => { - if (bt === 'upload') { - this.fileElement.click(); - } - - if (bt === 'doodle') { - this.props.onOpenDoodle(); - } - - this.dropdown.setState({ open: false }); - }; - - handleFileChange = (e) => { - if (e.target.files.length > 0) { - this.props.onSelectFile(e.target.files); - } - } - - setFileRef = (c) => { - this.fileElement = c; - } - - setDropdownRef = (c) => { - this.dropdown = c; - } - - render () { - const { intl, resetFileKey, disabled, acceptContentTypes } = this.props; - - const options = [ - { icon: 'cloud-upload', text: messages.upload, name: 'upload' }, - { icon: 'paint-brush', text: messages.doodle, name: 'doodle' }, - ]; - - const optionElems = options.map((item) => { - const hdl = () => this.handleItemClick(item.name); - return ( - <div - role='button' - tabIndex='0' - key={item.name} - onClick={hdl} - className='privacy-dropdown__option' - > - <div className='privacy-dropdown__option__icon'> - <i className={`fa fa-fw fa-${item.icon}`} /> - </div> - - <div className='privacy-dropdown__option__content'> - <strong>{intl.formatMessage(item.text)}</strong> - </div> - </div> - ); - }); - - return ( - <div> - <ComposeDropdown - title={intl.formatMessage(messages.attach)} - icon='paperclip' - disabled={disabled} - ref={this.setDropdownRef} - > - {optionElems} - </ComposeDropdown> - <input - key={resetFileKey} - ref={this.setFileRef} - type='file' - multiple={false} - accept={acceptContentTypes.toArray().join(',')} - onChange={this.handleFileChange} - disabled={disabled} - style={{ display: 'none' }} - /> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js b/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js deleted file mode 100644 index 3d474af30..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/autosuggest_account.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import Avatar from 'flavours/glitch/components/avatar'; -import DisplayName from 'flavours/glitch/components/display_name'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class AutosuggestAccount extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.map.isRequired, - }; - - render () { - const { account } = this.props; - - return ( - <div className='autosuggest-account'> - <div className='autosuggest-account-icon'><Avatar account={account} size={18} /></div> - <DisplayName account={account} /> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/character_counter.js b/app/javascript/flavours/glitch/features/compose/components/character_counter.js deleted file mode 100644 index 0ecfc9141..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/character_counter.js +++ /dev/null @@ -1,25 +0,0 @@ -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 deleted file mode 100644 index 67ce935f4..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ /dev/null @@ -1,286 +0,0 @@ -import React from 'react'; -import CharacterCounter from './character_counter'; -import Button from 'flavours/glitch/components/button'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import ReplyIndicatorContainer from '../containers/reply_indicator_container'; -import AutosuggestTextarea from 'flavours/glitch/components/autosuggest_textarea'; -import { defineMessages, injectIntl } from 'react-intl'; -import Collapsable from 'flavours/glitch/components/collapsable'; -import SpoilerButtonContainer from '../containers/spoiler_button_container'; -import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; -import ComposeAdvancedOptionsContainer from '../containers/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'; -import WarningContainer from '../containers/warning_container'; -import { isMobile } from 'flavours/glitch/util/is_mobile'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { length } from 'stringz'; -import { countableText } from 'flavours/glitch/util/counter'; -import ComposeAttachOptions from './attach_options'; -import initialState from 'flavours/glitch/util/initial_state'; - -const maxChars = initialState.max_toot_chars; - -const messages = defineMessages({ - placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, - spoiler_placeholder: { id: 'compose_form.spoiler_placeholder', defaultMessage: 'Write your warning here' }, - publish: { id: 'compose_form.publish', defaultMessage: 'Toot' }, - publishLoud: { id: 'compose_form.publish_loud', defaultMessage: '{publish}!' }, -}); - -@injectIntl -export default class ComposeForm extends ImmutablePureComponent { - - static propTypes = { - intl: PropTypes.object.isRequired, - text: PropTypes.string.isRequired, - suggestion_token: PropTypes.string, - 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), - is_submitting: PropTypes.bool, - is_uploading: PropTypes.bool, - onChange: PropTypes.func.isRequired, - 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, - }; - - static defaultProps = { - showSearch: false, - }; - - handleChange = (e) => { - this.props.onChange(e.target.value); - } - - handleKeyDown = (e) => { - if (e.keyCode === 13 && (e.ctrlKey || e.metaKey)) { - this.handleSubmit(); - } - } - - 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) - // Update the state to match the current text - this.props.onChange(this.autosuggestTextarea.textarea.value); - } - - this.props.onSubmit(); - } - - onSuggestionsClearRequested = () => { - this.props.onClearSuggestions(); - } - - 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(); - } else if(prevProps.is_submitting && !this.props.is_submitting) { - this.autosuggestTextarea.textarea.focus(); - } - } - - setAutosuggestTextarea = (c) => { - this.autosuggestTextarea = c; - } - - handleEmojiPick = (data) => { - const position = this.autosuggestTextarea.textarea.selectionStart; - const emojiChar = data.native; - this._restoreCaret = position + emojiChar.length + 1; - this.props.onPickEmoji(position, data); - } - - render () { - const { intl, onPaste, showSearch } = this.props; - const disabled = this.props.is_submitting; - 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'); - let showSideArm = secondaryVisibility !== 'none'; - - let publishText = ''; - let publishText2 = ''; - let title = ''; - let title2 = ''; - - const privacyIcons = { - none: '', - public: 'globe', - unlisted: 'unlock-alt', - private: 'lock', - direct: 'envelope', - }; - - title = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${this.props.privacy}.short` })}`; - - if (showSideArm) { - // Enhanced behavior with dual toot buttons - publishText = ( - <span> - { - <i - className={`fa fa-${privacyIcons[this.props.privacy]}`} - style={{ paddingRight: '5px' }} - /> - }{intl.formatMessage(messages.publish)} - </span> - ); - - title2 = `${intl.formatMessage(messages.publish)}: ${intl.formatMessage({ id: `privacy.${secondaryVisibility}.short` })}`; - publishText2 = ( - <i - className={`fa fa-${privacyIcons[secondaryVisibility]}`} - aria-label={title2} - /> - ); - } else { - // Original vanilla behavior - no icon if public or unlisted - 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) > maxChars || (text.length !== 0 && text.trim().length === 0); - - return ( - <div className='compose-form'> - <Collapsable isVisible={this.props.spoiler} fullHeight={50}> - <div className='spoiler-input'> - <label> - <span style={{ display: 'none' }}>{intl.formatMessage(messages.spoiler_placeholder)}</span> - <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' /> - </label> - </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} - autoFocus={!showSearch && !isMobile(window.innerWidth)} - /> - - <EmojiPickerDropdown onPickEmoji={this.handleEmojiPick} /> - </div> - - <div className='compose-form__modifiers'> - <UploadFormContainer /> - </div> - - <div className='compose-form__buttons'> - <ComposeAttachOptions /> - <SensitiveButtonContainer /> - <div className='compose-form__buttons-separator' /> - <PrivacyDropdownContainer /> - <SpoilerButtonContainer /> - <ComposeAdvancedOptionsContainer /> - </div> - - <div className='compose-form__publish'> - <div className='character-counter__wrapper'><CharacterCounter max={maxChars} text={text} /></div> - <div className='compose-form__publish-button-wrapper'> - { - showSideArm ? - <Button - className='compose-form__publish__side-arm' - text={publishText2} - title={title2} - onClick={this.handleSubmit2} - disabled={submitDisabled} - /> : '' - } - <Button - className='compose-form__publish__primary' - text={publishText} - title={title} - onClick={this.handleSubmit} - disabled={submitDisabled} - /> - </div> - </div> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js deleted file mode 100644 index 1b0000fb7..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ /dev/null @@ -1,77 +0,0 @@ -// Package imports. -import React from 'react'; -import PropTypes from 'prop-types'; - -// Our imports. -import IconButton from 'flavours/glitch/components/icon_button'; - -const iconStyle = { - height : null, - lineHeight : '27px', -}; - -export default class ComposeDropdown extends React.PureComponent { - - static propTypes = { - title: PropTypes.string.isRequired, - icon: PropTypes.string, - highlight: PropTypes.bool, - disabled: PropTypes.bool, - children: PropTypes.arrayOf(PropTypes.node).isRequired, - }; - - state = { - open: false, - }; - - 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); - } - - onToggleDropdown = () => { - if (this.props.disabled) return; - this.setState({ open: !this.state.open }); - }; - - setRef = (c) => { - this.node = c; - }; - - render () { - const { open } = this.state; - let { highlight, title, icon, disabled } = this.props; - - if (!icon) icon = 'ellipsis-h'; - - return ( - <div ref={this.setRef} className={`advanced-options-dropdown ${open ? 'open' : ''} ${highlight ? 'active' : ''} `}> - <div className='advanced-options-dropdown__value'> - <IconButton - className={'inverted'} - title={title} - icon={icon} active={open || highlight} - size={18} - style={iconStyle} - disabled={disabled} - onClick={this.onToggleDropdown} - /> - </div> - <div className='advanced-options-dropdown__dropdown'> - {this.props.children} - </div> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js deleted file mode 100644 index cf89f91d3..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/emoji_picker_dropdown.js +++ /dev/null @@ -1,376 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl } from 'react-intl'; -import { EmojiPicker as EmojiPickerAsync } from 'flavours/glitch/util/async-components'; -import Overlay from 'react-overlays/lib/Overlay'; -import classNames from 'classnames'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import detectPassiveEvents from 'detect-passive-events'; -import { buildCustomEmojis } from 'flavours/glitch/util/emoji'; - -const messages = defineMessages({ - emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' }, - emoji_search: { id: 'emoji_button.search', defaultMessage: 'Search...' }, - emoji_not_found: { id: 'emoji_button.not_found', defaultMessage: 'No emojos!! (╯°□°)╯︵ ┻━┻' }, - custom: { id: 'emoji_button.custom', defaultMessage: 'Custom' }, - recent: { id: 'emoji_button.recent', defaultMessage: 'Frequently used' }, - search_results: { id: 'emoji_button.search_results', defaultMessage: 'Search results' }, - 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 assetHost = process.env.CDN_HOST || ''; -let EmojiPicker, Emoji; // load asynchronously - -const backgroundImageFn = () => `${assetHost}/emoji/sheet.png`; -const listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; - -const categoriesSort = [ - 'recent', - 'custom', - 'people', - 'nature', - 'foods', - 'activity', - 'places', - 'objects', - 'symbols', - 'flags', -]; - -class ModifierPickerMenu extends React.PureComponent { - - static propTypes = { - active: PropTypes.bool, - onSelect: PropTypes.func.isRequired, - onClose: PropTypes.func.isRequired, - }; - - handleClick = e => { - this.props.onSelect(e.currentTarget.getAttribute('data-index') * 1); - } - - componentWillReceiveProps (nextProps) { - if (nextProps.active) { - this.attachListeners(); - } else { - this.removeListeners(); - } - } - - componentWillUnmount () { - this.removeListeners(); - } - - handleDocumentClick = e => { - if (this.node && !this.node.contains(e.target)) { - this.props.onClose(); - } - } - - attachListeners () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - removeListeners () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - setRef = c => { - this.node = c; - } - - render () { - const { active } = this.props; - - return ( - <div className='emoji-picker-dropdown__modifiers__menu' style={{ display: active ? 'block' : 'none' }} ref={this.setRef}> - <button onClick={this.handleClick} data-index={1}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={1} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={2}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={2} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={3}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={3} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={4}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={4} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={5}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={5} backgroundImageFn={backgroundImageFn} /></button> - <button onClick={this.handleClick} data-index={6}><Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={6} backgroundImageFn={backgroundImageFn} /></button> - </div> - ); - } - -} - -class ModifierPicker extends React.PureComponent { - - static propTypes = { - active: PropTypes.bool, - modifier: PropTypes.number, - onChange: PropTypes.func, - onClose: PropTypes.func, - onOpen: PropTypes.func, - }; - - handleClick = () => { - if (this.props.active) { - this.props.onClose(); - } else { - this.props.onOpen(); - } - } - - handleSelect = modifier => { - this.props.onChange(modifier); - this.props.onClose(); - } - - render () { - const { active, modifier } = this.props; - - return ( - <div className='emoji-picker-dropdown__modifiers'> - <Emoji emoji='fist' set='twitter' size={22} sheetSize={32} skin={modifier} onClick={this.handleClick} backgroundImageFn={backgroundImageFn} /> - <ModifierPickerMenu active={active} onSelect={this.handleSelect} onClose={this.props.onClose} /> - </div> - ); - } - -} - -@injectIntl -class EmojiPickerMenu extends React.PureComponent { - - static propTypes = { - custom_emojis: ImmutablePropTypes.list, - frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), - loading: PropTypes.bool, - onClose: PropTypes.func.isRequired, - onPick: PropTypes.func.isRequired, - style: PropTypes.object, - placement: PropTypes.string, - arrowOffsetLeft: PropTypes.string, - arrowOffsetTop: PropTypes.string, - intl: PropTypes.object.isRequired, - skinTone: PropTypes.number.isRequired, - onSkinTone: PropTypes.func.isRequired, - }; - - static defaultProps = { - style: {}, - loading: true, - placement: 'bottom', - frequentlyUsedEmojis: [], - }; - - state = { - modifierOpen: false, - }; - - handleDocumentClick = e => { - if (this.node && !this.node.contains(e.target)) { - this.props.onClose(); - } - } - - componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - setRef = c => { - this.node = c; - } - - getI18n = () => { - const { intl } = this.props; - - return { - search: intl.formatMessage(messages.emoji_search), - notfound: intl.formatMessage(messages.emoji_not_found), - categories: { - search: intl.formatMessage(messages.search_results), - recent: intl.formatMessage(messages.recent), - people: intl.formatMessage(messages.people), - nature: intl.formatMessage(messages.nature), - foods: intl.formatMessage(messages.food), - activity: intl.formatMessage(messages.activity), - places: intl.formatMessage(messages.travel), - objects: intl.formatMessage(messages.objects), - symbols: intl.formatMessage(messages.symbols), - flags: intl.formatMessage(messages.flags), - custom: intl.formatMessage(messages.custom), - }, - }; - } - - handleClick = emoji => { - if (!emoji.native) { - emoji.native = emoji.colons; - } - - this.props.onClose(); - this.props.onPick(emoji); - } - - handleModifierOpen = () => { - this.setState({ modifierOpen: true }); - } - - handleModifierClose = () => { - this.setState({ modifierOpen: false }); - } - - handleModifierChange = modifier => { - this.props.onSkinTone(modifier); - } - - render () { - const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props; - - if (loading) { - return <div style={{ width: 299 }} />; - } - - const title = intl.formatMessage(messages.emoji); - const { modifierOpen } = this.state; - - return ( - <div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}> - <EmojiPicker - perLine={8} - emojiSize={22} - sheetSize={32} - custom={buildCustomEmojis(custom_emojis)} - color='' - emoji='' - set='twitter' - title={title} - i18n={this.getI18n()} - onClick={this.handleClick} - include={categoriesSort} - recent={frequentlyUsedEmojis} - skin={skinTone} - showPreview={false} - backgroundImageFn={backgroundImageFn} - emojiTooltip - /> - - <ModifierPicker - active={modifierOpen} - modifier={skinTone} - onOpen={this.handleModifierOpen} - onClose={this.handleModifierClose} - onChange={this.handleModifierChange} - /> - </div> - ); - } - -} - -@injectIntl -export default class EmojiPickerDropdown extends React.PureComponent { - - static propTypes = { - custom_emojis: ImmutablePropTypes.list, - frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string), - intl: PropTypes.object.isRequired, - onPickEmoji: PropTypes.func.isRequired, - onSkinTone: PropTypes.func.isRequired, - skinTone: PropTypes.number.isRequired, - }; - - state = { - active: false, - loading: false, - }; - - setRef = (c) => { - this.dropdown = c; - } - - onShowDropdown = () => { - this.setState({ active: true }); - - if (!EmojiPicker) { - this.setState({ loading: true }); - - EmojiPickerAsync().then(EmojiMart => { - EmojiPicker = EmojiMart.Picker; - Emoji = EmojiMart.Emoji; - - this.setState({ loading: false }); - }).catch(() => { - this.setState({ loading: false }); - }); - } - } - - onHideDropdown = () => { - this.setState({ active: false }); - } - - onToggle = (e) => { - if (!this.state.loading && (!e.key || e.key === 'Enter')) { - if (this.state.active) { - this.onHideDropdown(); - } else { - this.onShowDropdown(); - } - } - } - - handleKeyDown = e => { - if (e.key === 'Escape') { - this.onHideDropdown(); - } - } - - setTargetRef = c => { - this.target = c; - } - - findTarget = () => { - return this.target; - } - - render () { - const { intl, onPickEmoji, onSkinTone, skinTone, frequentlyUsedEmojis } = this.props; - const title = intl.formatMessage(messages.emoji); - const { active, loading } = this.state; - - return ( - <div className='emoji-picker-dropdown' onKeyDown={this.handleKeyDown}> - <div ref={this.setTargetRef} className='emoji-button' title={title} aria-label={title} aria-expanded={active} role='button' onClick={this.onToggle} onKeyDown={this.onToggle} tabIndex={0}> - <img - className={classNames('emojione', { 'pulse-loading': active && loading })} - alt='🙂' - src={`${assetHost}/emoji/1f602.svg`} - /> - </div> - - <Overlay show={active} placement='bottom' target={this.findTarget}> - <EmojiPickerMenu - custom_emojis={this.props.custom_emojis} - loading={loading} - onClose={this.onHideDropdown} - onPick={onPickEmoji} - onSkinTone={onSkinTone} - skinTone={skinTone} - frequentlyUsedEmojis={frequentlyUsedEmojis} - /> - </Overlay> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js b/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js deleted file mode 100644 index 1b6d74123..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/navigation_bar.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import Avatar from 'flavours/glitch/components/avatar'; -import IconButton from 'flavours/glitch/components/icon_button'; -import Permalink from 'flavours/glitch/components/permalink'; -import { FormattedMessage } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class NavigationBar extends ImmutablePureComponent { - - static propTypes = { - account: ImmutablePropTypes.map.isRequired, - onClose: PropTypes.func.isRequired, - }; - - render () { - return ( - <div className='navigation-bar'> - <Permalink href={this.props.account.get('url')} to={`/accounts/${this.props.account.get('id')}`}> - <span style={{ display: 'none' }}>{this.props.account.get('acct')}</span> - <Avatar account={this.props.account} 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> - - <IconButton title='' icon='close' onClick={this.props.onClose} /> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js deleted file mode 100644 index 90f062f8f..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js +++ /dev/null @@ -1,200 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { injectIntl, defineMessages } from 'react-intl'; -import IconButton from 'flavours/glitch/components/icon_button'; -import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; -import detectPassiveEvents from 'detect-passive-events'; -import classNames from 'classnames'; - -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 listenerOptions = detectPassiveEvents.hasSupport ? { passive: true } : false; - -class PrivacyDropdownMenu extends React.PureComponent { - - static propTypes = { - style: PropTypes.object, - items: PropTypes.array.isRequired, - value: PropTypes.string.isRequired, - onClose: PropTypes.func.isRequired, - onChange: PropTypes.func.isRequired, - }; - - handleDocumentClick = e => { - if (this.node && !this.node.contains(e.target)) { - this.props.onClose(); - } - } - - handleClick = e => { - if (e.key === 'Escape') { - this.props.onClose(); - } else if (!e.key || e.key === 'Enter') { - const value = e.currentTarget.getAttribute('data-index'); - - e.preventDefault(); - - this.props.onClose(); - this.props.onChange(value); - } - } - - componentDidMount () { - document.addEventListener('click', this.handleDocumentClick, false); - document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - componentWillUnmount () { - document.removeEventListener('click', this.handleDocumentClick, false); - document.removeEventListener('touchend', this.handleDocumentClick, listenerOptions); - } - - setRef = c => { - this.node = c; - } - - render () { - const { style, items, value } = this.props; - - return ( - <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> - {({ opacity, scaleX, scaleY }) => ( - <div className='privacy-dropdown__dropdown' style={{ ...style, opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }} ref={this.setRef}> - {items.map(item => - <div role='button' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleClick} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })}> - <div className='privacy-dropdown__option__icon'> - <i className={`fa fa-fw fa-${item.icon}`} /> - </div> - - <div className='privacy-dropdown__option__content'> - <strong>{item.text}</strong> - {item.meta} - </div> - </div> - )} - </div> - )} - </Motion> - ); - } - -} - -@injectIntl -export default class PrivacyDropdown extends React.PureComponent { - - static propTypes = { - isUserTouching: PropTypes.func, - isModalOpen: PropTypes.bool.isRequired, - onModalOpen: PropTypes.func, - onModalClose: PropTypes.func, - value: PropTypes.string.isRequired, - onChange: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - state = { - open: false, - }; - - handleToggle = () => { - if (this.props.isUserTouching()) { - if (this.state.open) { - this.props.onModalClose(); - } else { - this.props.onModalOpen({ - actions: this.options.map(option => ({ ...option, active: option.value === this.props.value })), - onClick: this.handleModalActionClick, - }); - } - } else { - this.setState({ open: !this.state.open }); - } - } - - handleModalActionClick = (e) => { - e.preventDefault(); - - const { value } = this.options[e.currentTarget.getAttribute('data-index')]; - - this.props.onModalClose(); - this.props.onChange(value); - } - - handleKeyDown = e => { - switch(e.key) { - case 'Enter': - this.handleToggle(); - break; - case 'Escape': - this.handleClose(); - break; - } - } - - handleClose = () => { - this.setState({ open: false }); - } - - handleChange = value => { - this.props.onChange(value); - } - - componentWillMount () { - const { intl: { formatMessage } } = this.props; - - this.options = [ - { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, - { icon: 'unlock-alt', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, - { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, - { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, - ]; - } - - render () { - const { value, intl } = this.props; - const { open } = this.state; - - const valueOption = this.options.find(item => item.value === value); - - return ( - <div className={classNames('privacy-dropdown', { active: open })} onKeyDown={this.handleKeyDown}> - <div className={classNames('privacy-dropdown__value', { active: this.options.indexOf(valueOption) === 0 })}> - <IconButton - className='privacy-dropdown__value-icon' - icon={valueOption.icon} - title={intl.formatMessage(messages.change_privacy)} - size={18} - expanded={open} - active={open} - inverted - onClick={this.handleToggle} - style={{ height: null, lineHeight: '27px' }} - /> - </div> - - <Overlay show={open} placement='bottom' target={this}> - <PrivacyDropdownMenu - items={this.options} - value={value} - onClose={this.handleClose} - onChange={this.handleChange} - /> - </Overlay> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js b/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js deleted file mode 100644 index 3048d591b..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/reply_indicator.js +++ /dev/null @@ -1,67 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import Avatar from 'flavours/glitch/components/avatar'; -import IconButton from 'flavours/glitch/components/icon_button'; -import DisplayName from 'flavours/glitch/components/display_name'; -import { defineMessages, injectIntl } from 'react-intl'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import { isRtl } from 'flavours/glitch/util/rtl'; - -const messages = defineMessages({ - cancel: { id: 'reply_indicator.cancel', defaultMessage: 'Cancel' }, -}); - -@injectIntl -export default class ReplyIndicator extends ImmutablePureComponent { - - static contextTypes = { - router: PropTypes.object, - }; - - static propTypes = { - status: ImmutablePropTypes.map, - onCancel: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - handleClick = () => { - this.props.onCancel(); - } - - handleAccountClick = (e) => { - if (e.button === 0) { - e.preventDefault(); - this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`); - } - } - - render () { - const { status, intl } = this.props; - - if (!status) { - return null; - } - - const content = { __html: status.get('contentHtml') }; - const style = { - direction: isRtl(status.get('search_index')) ? 'rtl' : 'ltr', - }; - - 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 account={status.get('account')} size={24} /></div> - <DisplayName account={status.get('account')} /> - </a> - </div> - - <div className='reply-indicator__content' style={style} dangerouslySetInnerHTML={content} /> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/search.js b/app/javascript/flavours/glitch/features/compose/components/search.js deleted file mode 100644 index 1ce66b19d..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/search.js +++ /dev/null @@ -1,129 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; -import Overlay from 'react-overlays/lib/Overlay'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; - -const messages = defineMessages({ - placeholder: { id: 'search.placeholder', defaultMessage: 'Search' }, -}); - -class SearchPopout extends React.PureComponent { - - static propTypes = { - style: PropTypes.object, - }; - - render () { - const { style } = this.props; - - return ( - <div style={{ ...style, position: 'absolute', width: 285 }}> - <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> - {({ opacity, scaleX, scaleY }) => ( - <div className='search-popout' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> - <h4><FormattedMessage id='search_popout.search_format' defaultMessage='Advanced search format' /></h4> - - <ul> - <li><em>#example</em> <FormattedMessage id='search_popout.tips.hashtag' defaultMessage='hashtag' /></li> - <li><em>@username@domain</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> - <li><em>URL</em> <FormattedMessage id='search_popout.tips.user' defaultMessage='user' /></li> - <li><em>URL</em> <FormattedMessage id='search_popout.tips.status' defaultMessage='status' /></li> - </ul> - - <FormattedMessage id='search_popout.tips.text' defaultMessage='Simple text returns matching display names, usernames and hashtags' /> - </div> - )} - </Motion> - </div> - ); - } - -} - -@injectIntl -export default class Search extends React.PureComponent { - - static 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, - }; - - state = { - expanded: false, - }; - - 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(); - } else if (e.key === 'Escape') { - document.querySelector('.ui').parentElement.focus(); - } - } - - noop () { - - } - - handleFocus = () => { - this.setState({ expanded: true }); - this.props.onShow(); - } - - handleBlur = () => { - this.setState({ expanded: false }); - } - - render () { - const { intl, value, submitted } = this.props; - const { expanded } = this.state; - const hasValue = value.length > 0 || submitted; - - return ( - <div className='search'> - <label> - <span style={{ display: 'none' }}>{intl.formatMessage(messages.placeholder)}</span> - <input - className='search__input' - type='text' - placeholder={intl.formatMessage(messages.placeholder)} - value={value} - onChange={this.handleChange} - onKeyUp={this.handleKeyDown} - onFocus={this.handleFocus} - onBlur={this.handleBlur} - /> - </label> - - <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> - - <Overlay show={expanded && !hasValue} placement='bottom' target={this}> - <SearchPopout /> - </Overlay> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/search_results.js b/app/javascript/flavours/glitch/features/compose/components/search_results.js deleted file mode 100644 index 2a4818d4e..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/search_results.js +++ /dev/null @@ -1,65 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { FormattedMessage } from 'react-intl'; -import AccountContainer from 'flavours/glitch/containers/account_container'; -import StatusContainer from 'flavours/glitch/containers/status_container'; -import { Link } from 'react-router-dom'; -import ImmutablePureComponent from 'react-immutable-pure-component'; - -export default class SearchResults extends ImmutablePureComponent { - - static propTypes = { - results: ImmutablePropTypes.map.isRequired, - }; - - 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 key={hashtag} 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> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js b/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js deleted file mode 100644 index 9c8ffab1f..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/text_icon_button.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -export default class TextIconButton extends React.PureComponent { - - static propTypes = { - label: PropTypes.string.isRequired, - title: PropTypes.string, - active: PropTypes.bool, - onClick: PropTypes.func.isRequired, - ariaControls: PropTypes.string, - }; - - 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> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload.js b/app/javascript/flavours/glitch/features/compose/components/upload.js deleted file mode 100644 index a1fc93234..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/upload.js +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import PropTypes from 'prop-types'; -import IconButton from 'flavours/glitch/components/icon_button'; -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 } from 'react-intl'; -import classNames from 'classnames'; - -const messages = defineMessages({ - undo: { id: 'upload_form.undo', defaultMessage: 'Undo' }, - description: { id: 'upload_form.description', defaultMessage: 'Describe for the visually impaired' }, -}); - -@injectIntl -export default class Upload extends ImmutablePureComponent { - - static propTypes = { - media: ImmutablePropTypes.map.isRequired, - intl: PropTypes.object.isRequired, - onUndo: PropTypes.func.isRequired, - onDescriptionChange: PropTypes.func.isRequired, - }; - - state = { - hovered: false, - focused: false, - dirtyDescription: null, - }; - - handleUndoClick = () => { - this.props.onUndo(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 }); - } - - 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; - const description = this.state.dirtyDescription || media.get('description') || ''; - - return ( - <div className='compose-form__upload' onMouseEnter={this.handleMouseEnter} onMouseLeave={this.handleMouseLeave}> - <Motion defaultStyle={{ scale: 0.8 }} style={{ scale: spring(1, { stiffness: 180, damping: 12 }) }}> - {({ scale }) => ( - <div className='compose-form__upload-thumbnail' style={{ transform: `scale(${scale})`, backgroundImage: `url(${media.get('preview_url')})` }}> - <IconButton icon='times' title={intl.formatMessage(messages.undo)} size={36} onClick={this.handleUndoClick} /> - - <div className={classNames('compose-form__upload-description', { active })}> - <label> - <span style={{ display: 'none' }}>{intl.formatMessage(messages.description)}</span> - - <input - placeholder={intl.formatMessage(messages.description)} - type='text' - value={description} - maxLength={420} - onFocus={this.handleInputFocus} - onChange={this.handleInputChange} - onBlur={this.handleInputBlur} - /> - </label> - </div> - </div> - )} - </Motion> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_button.js b/app/javascript/flavours/glitch/features/compose/components/upload_button.js deleted file mode 100644 index f06167a2a..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/upload_button.js +++ /dev/null @@ -1,77 +0,0 @@ -import React from 'react'; -import IconButton from 'flavours/glitch/components/icon_button'; -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'; - -const messages = defineMessages({ - upload: { id: 'upload_button.label', defaultMessage: 'Add media' }, -}); - -const makeMapStateToProps = () => { - const mapStateToProps = state => ({ - acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']), - }); - - return mapStateToProps; -}; - -const iconStyle = { - height: null, - lineHeight: '27px', -}; - -@connect(makeMapStateToProps) -@injectIntl -export default class UploadButton extends ImmutablePureComponent { - - static propTypes = { - disabled: PropTypes.bool, - onSelectFile: PropTypes.func.isRequired, - style: PropTypes.object, - resetFileKey: PropTypes.number, - acceptContentTypes: ImmutablePropTypes.listOf(PropTypes.string).isRequired, - intl: PropTypes.object.isRequired, - }; - - 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, acceptContentTypes } = 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} /> - <label> - <span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span> - <input - key={resetFileKey} - ref={this.setRef} - type='file' - multiple={false} - accept={acceptContentTypes.toArray().join(',')} - onChange={this.handleChange} - disabled={disabled} - style={{ display: 'none' }} - /> - </label> - </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 deleted file mode 100644 index b7f112205..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/upload_form.js +++ /dev/null @@ -1,29 +0,0 @@ -import React from 'react'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import UploadProgressContainer from '../containers/upload_progress_container'; -import ImmutablePureComponent from 'react-immutable-pure-component'; -import UploadContainer from '../containers/upload_container'; - -export default class UploadForm extends ImmutablePureComponent { - - static propTypes = { - mediaIds: ImmutablePropTypes.list.isRequired, - }; - - render () { - const { mediaIds } = this.props; - - return ( - <div className='compose-form__upload-wrapper'> - <UploadProgressContainer /> - - <div className='compose-form__uploads-wrapper'> - {mediaIds.map(id => ( - <UploadContainer id={id} key={id} /> - ))} - </div> - </div> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js b/app/javascript/flavours/glitch/features/compose/components/upload_progress.js deleted file mode 100644 index 2a3b8ceb4..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/upload_progress.js +++ /dev/null @@ -1,42 +0,0 @@ -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'; - -export default class UploadProgress extends React.PureComponent { - - static propTypes = { - active: PropTypes.bool, - progress: PropTypes.number, - }; - - 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> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/components/warning.js b/app/javascript/flavours/glitch/features/compose/components/warning.js deleted file mode 100644 index 4962e76c8..000000000 --- a/app/javascript/flavours/glitch/features/compose/components/warning.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; - -export default class Warning extends React.PureComponent { - - static propTypes = { - message: PropTypes.node.isRequired, - }; - - render () { - const { message } = this.props; - - return ( - <Motion defaultStyle={{ opacity: 0, scaleX: 0.85, scaleY: 0.75 }} style={{ opacity: spring(1, { damping: 35, stiffness: 400 }), scaleX: spring(1, { damping: 35, stiffness: 400 }), scaleY: spring(1, { damping: 35, stiffness: 400 }) }}> - {({ opacity, scaleX, scaleY }) => ( - <div className='compose-form__warning' style={{ opacity: opacity, transform: `scale(${scaleX}, ${scaleY})` }}> - {message} - </div> - )} - </Motion> - ); - } - -} diff --git a/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js b/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js deleted file mode 100644 index da381568b..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/advanced_options_container.js +++ /dev/null @@ -1,20 +0,0 @@ -// Package imports. -import { connect } from 'react-redux'; - -// Our imports. -import { toggleComposeAdvancedOption } from 'flavours/glitch/actions/compose'; -import ComposeAdvancedOptions from '../components/advanced_options'; - -const mapStateToProps = state => ({ - values: state.getIn(['compose', 'advanced_options']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (option) { - dispatch(toggleComposeAdvancedOption(option)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(ComposeAdvancedOptions); diff --git a/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js b/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js deleted file mode 100644 index 0e1c328fe..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/autosuggest_account_container.js +++ /dev/null @@ -1,15 +0,0 @@ -import { connect } from 'react-redux'; -import AutosuggestAccount from '../components/autosuggest_account'; -import { makeGetAccount } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/compose_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js deleted file mode 100644 index e2e93e44b..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/compose_form_container.js +++ /dev/null @@ -1,71 +0,0 @@ -import { connect } from 'react-redux'; -import ComposeForm from '../components/compose_form'; -import { changeComposeVisibility, uploadCompose } from 'flavours/glitch/actions/compose'; -import { - changeCompose, - submitCompose, - clearComposeSuggestions, - fetchComposeSuggestions, - selectComposeSuggestion, - changeComposeSpoilerText, - insertEmojiCompose, -} from 'flavours/glitch/actions/compose'; - -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']), - focusDate: state.getIn(['compose', 'focusDate']), - preselectDate: state.getIn(['compose', 'preselectDate']), - is_submitting: state.getIn(['compose', 'is_submitting']), - is_uploading: state.getIn(['compose', 'is_uploading']), - 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) => ({ - - onChange (text) { - dispatch(changeCompose(text)); - }, - - onPrivacyChange (value) { - dispatch(changeComposeVisibility(value)); - }, - - 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/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js deleted file mode 100644 index ba85edd87..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/emoji_picker_dropdown_container.js +++ /dev/null @@ -1,82 +0,0 @@ -import { connect } from 'react-redux'; -import EmojiPickerDropdown from '../components/emoji_picker_dropdown'; -import { changeSetting } from 'flavours/glitch/actions/settings'; -import { createSelector } from 'reselect'; -import { Map as ImmutableMap } from 'immutable'; -import { useEmoji } from 'flavours/glitch/actions/emojis'; - -const perLine = 8; -const lines = 2; - -const DEFAULTS = [ - '+1', - 'grinning', - 'kissing_heart', - 'heart_eyes', - 'laughing', - 'stuck_out_tongue_winking_eye', - 'sweat_smile', - 'joy', - 'yum', - 'disappointed', - 'thinking_face', - 'weary', - 'sob', - 'sunglasses', - 'heart', - 'ok_hand', -]; - -const getFrequentlyUsedEmojis = createSelector([ - state => state.getIn(['settings', 'frequentlyUsedEmojis'], ImmutableMap()), -], emojiCounters => { - let emojis = emojiCounters - .keySeq() - .sort((a, b) => emojiCounters.get(a) - emojiCounters.get(b)) - .reverse() - .slice(0, perLine * lines) - .toArray(); - - if (emojis.length < DEFAULTS.length) { - emojis = emojis.concat(DEFAULTS.slice(0, DEFAULTS.length - emojis.length)); - } - - return emojis; -}); - -const getCustomEmojis = createSelector([ - state => state.get('custom_emojis'), -], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => { - const aShort = a.get('shortcode').toLowerCase(); - const bShort = b.get('shortcode').toLowerCase(); - - if (aShort < bShort) { - return -1; - } else if (aShort > bShort ) { - return 1; - } else { - return 0; - } -})); - -const mapStateToProps = state => ({ - custom_emojis: getCustomEmojis(state), - skinTone: state.getIn(['settings', 'skinTone']), - frequentlyUsedEmojis: getFrequentlyUsedEmojis(state), -}); - -const mapDispatchToProps = (dispatch, { onPickEmoji }) => ({ - onSkinTone: skinTone => { - dispatch(changeSetting(['skinTone'], skinTone)); - }, - - onPickEmoji: emoji => { - dispatch(useEmoji(emoji)); - - if (onPickEmoji) { - onPickEmoji(emoji); - } - }, -}); - -export default connect(mapStateToProps, mapDispatchToProps)(EmojiPickerDropdown); diff --git a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js b/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js deleted file mode 100644 index eb630ffbb..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/navigation_container.js +++ /dev/null @@ -1,11 +0,0 @@ -import { connect } from 'react-redux'; -import NavigationBar from '../components/navigation_bar'; -import { me } from 'flavours/glitch/util/initial_state'; - -const mapStateToProps = state => { - return { - account: state.getIn(['accounts', me]), - }; -}; - -export default connect(mapStateToProps)(NavigationBar); diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js deleted file mode 100644 index cb94fcc80..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import PrivacyDropdown from '../components/privacy_dropdown'; -import { changeComposeVisibility } from 'flavours/glitch/actions/compose'; -import { openModal, closeModal } from 'flavours/glitch/actions/modal'; -import { isUserTouching } from 'flavours/glitch/util/is_mobile'; - -const mapStateToProps = state => ({ - isModalOpen: state.get('modal').modalType === 'ACTIONS', - value: state.getIn(['compose', 'privacy']), -}); - -const mapDispatchToProps = dispatch => ({ - - onChange (value) { - dispatch(changeComposeVisibility(value)); - }, - - isUserTouching, - onModalOpen: props => dispatch(openModal('ACTIONS', props)), - onModalClose: () => dispatch(closeModal()), - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js b/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js deleted file mode 100644 index a7c82d135..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/reply_indicator_container.js +++ /dev/null @@ -1,24 +0,0 @@ -import { connect } from 'react-redux'; -import { cancelReplyCompose } from 'flavours/glitch/actions/compose'; -import { makeGetStatus } from 'flavours/glitch/selectors'; -import ReplyIndicator from '../components/reply_indicator'; - -const makeMapStateToProps = () => { - const getStatus = makeGetStatus(); - - const mapStateToProps = state => ({ - 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/javascript/flavours/glitch/features/compose/containers/search_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_container.js deleted file mode 100644 index 8f4bfcf08..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/search_container.js +++ /dev/null @@ -1,35 +0,0 @@ -import { connect } from 'react-redux'; -import { - changeSearch, - clearSearch, - submitSearch, - showSearch, -} from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/search_results_container.js b/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js deleted file mode 100644 index 16d95d417..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/search_results_container.js +++ /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/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js deleted file mode 100644 index cf6706c0e..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/sensitive_button_container.js +++ /dev/null @@ -1,71 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import PropTypes from 'prop-types'; -import classNames from 'classnames'; -import IconButton from 'flavours/glitch/components/icon_button'; -import { changeComposeSensitivity } from 'flavours/glitch/actions/compose'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; -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']), - disabled: state.getIn(['compose', 'spoiler']), -}); - -const mapDispatchToProps = dispatch => ({ - - onClick () { - dispatch(changeComposeSensitivity()); - }, - -}); - -class SensitiveButton extends React.PureComponent { - - static propTypes = { - visible: PropTypes.bool, - active: PropTypes.bool, - disabled: PropTypes.bool, - onClick: PropTypes.func.isRequired, - intl: PropTypes.object.isRequired, - }; - - render () { - const { visible, active, disabled, onClick, intl } = this.props; - - return ( - <Motion defaultStyle={{ scale: 0.87 }} style={{ scale: spring(visible ? 1 : 0.87, { stiffness: 200, damping: 3 }) }}> - {({ scale }) => { - const icon = active ? 'eye-slash' : 'eye'; - const className = classNames('compose-form__sensitive-button', { - 'compose-form__sensitive-button--visible': visible, - }); - return ( - <div className={className} style={{ transform: `scale(${scale})` }}> - <IconButton - className='compose-form__sensitive-button__icon' - title={intl.formatMessage(messages.title)} - icon={icon} - onClick={onClick} - size={18} - active={active} - disabled={disabled} - style={{ lineHeight: null, height: null }} - inverted - /> - </div> - ); - }} - </Motion> - ); - } - -} - -export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(SensitiveButton)); diff --git a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js deleted file mode 100644 index d7b4246bc..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/spoiler_button_container.js +++ /dev/null @@ -1,25 +0,0 @@ -import { connect } from 'react-redux'; -import TextIconButton from '../components/text_icon_button'; -import { changeComposeSpoilerness } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/upload_button_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js deleted file mode 100644 index 4c1cb49e9..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_button_container.js +++ /dev/null @@ -1,18 +0,0 @@ -import { connect } from 'react-redux'; -import UploadButton from '../components/upload_button'; -import { uploadCompose } from 'flavours/glitch/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/javascript/flavours/glitch/features/compose/containers/upload_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_container.js deleted file mode 100644 index 368038425..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_container.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import Upload from '../components/upload'; -import { undoUploadCompose, changeUploadCompose } from 'flavours/glitch/actions/compose'; - -const mapStateToProps = (state, { id }) => ({ - media: state.getIn(['compose', 'media_attachments']).find(item => item.get('id') === id), -}); - -const mapDispatchToProps = dispatch => ({ - - onUndo: id => { - dispatch(undoUploadCompose(id)); - }, - - onDescriptionChange: (id, description) => { - dispatch(changeUploadCompose(id, description)); - }, - -}); - -export default connect(mapStateToProps, mapDispatchToProps)(Upload); diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js deleted file mode 100644 index a6798bf51..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_form_container.js +++ /dev/null @@ -1,8 +0,0 @@ -import { connect } from 'react-redux'; -import UploadForm from '../components/upload_form'; - -const mapStateToProps = state => ({ - mediaIds: state.getIn(['compose', 'media_attachments']).map(item => item.get('id')), -}); - -export default connect(mapStateToProps)(UploadForm); diff --git a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js b/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js deleted file mode 100644 index 0cfee96da..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/upload_progress_container.js +++ /dev/null @@ -1,9 +0,0 @@ -import { connect } from 'react-redux'; -import UploadProgress from '../components/upload_progress'; - -const mapStateToProps = state => ({ - active: state.getIn(['compose', 'is_uploading']), - progress: state.getIn(['compose', 'progress']), -}); - -export default connect(mapStateToProps)(UploadProgress); diff --git a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js b/app/javascript/flavours/glitch/features/compose/containers/warning_container.js deleted file mode 100644 index f20c75b91..000000000 --- a/app/javascript/flavours/glitch/features/compose/containers/warning_container.js +++ /dev/null @@ -1,24 +0,0 @@ -import React from 'react'; -import { connect } from 'react-redux'; -import Warning from '../components/warning'; -import PropTypes from 'prop-types'; -import { FormattedMessage } from 'react-intl'; -import { me } from 'flavours/glitch/util/initial_state'; - -const mapStateToProps = state => ({ - needsLockWarning: state.getIn(['compose', 'privacy']) === 'private' && !state.getIn(['accounts', me, 'locked']), -}); - -const WarningWrapper = ({ needsLockWarning }) => { - 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> }} />} />; - } - - return null; -}; - -WarningWrapper.propTypes = { - needsLockWarning: PropTypes.bool, -}; - -export default connect(mapStateToProps)(WarningWrapper); diff --git a/app/javascript/flavours/glitch/features/compose/index.js b/app/javascript/flavours/glitch/features/compose/index.js deleted file mode 100644 index 63c9500b1..000000000 --- a/app/javascript/flavours/glitch/features/compose/index.js +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react'; -import ComposeFormContainer from './containers/compose_form_container'; -import NavigationContainer from './containers/navigation_container'; -import PropTypes from 'prop-types'; -import ImmutablePropTypes from 'react-immutable-proptypes'; -import { connect } from 'react-redux'; -import { mountCompose, unmountCompose } from 'flavours/glitch/actions/compose'; -import { openModal } from 'flavours/glitch/actions/modal'; -import { changeLocalSetting } from 'flavours/glitch/actions/local_settings'; -import { Link } from 'react-router-dom'; -import { injectIntl, defineMessages } from 'react-intl'; -import SearchContainer from './containers/search_container'; -import Motion from 'flavours/glitch/util/optional_motion'; -import spring from 'react-motion/lib/spring'; -import SearchResultsContainer from './containers/search_results_container'; -import { changeComposing } from 'flavours/glitch/actions/compose'; - -const messages = defineMessages({ - start: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, - home_timeline: { id: 'tabs_bar.home', defaultMessage: 'Home' }, - 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' }, - settings: { id: 'navigation_bar.app_settings', defaultMessage: 'App settings' }, - logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' }, -}); - -const mapStateToProps = state => ({ - columns: state.getIn(['settings', 'columns']), - showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']), -}); - -@connect(mapStateToProps) -@injectIntl -export default class Compose extends React.PureComponent { - - static propTypes = { - dispatch: PropTypes.func.isRequired, - columns: ImmutablePropTypes.list.isRequired, - multiColumn: PropTypes.bool, - showSearch: PropTypes.bool, - intl: PropTypes.object.isRequired, - }; - - componentDidMount () { - this.props.dispatch(mountCompose()); - } - - componentWillUnmount () { - 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)); - } - - onBlur = () => { - this.props.dispatch(changeComposing(false)); - } - - render () { - const { multiColumn, showSearch, intl } = this.props; - - let header = ''; - - if (multiColumn) { - const { columns } = this.props; - header = ( - <nav className='drawer__header'> - <Link to='/getting-started' className='drawer__tab' title={intl.formatMessage(messages.start)} aria-label={intl.formatMessage(messages.start)}><i role='img' className='fa fa-fw fa-asterisk' /></Link> - {!columns.some(column => column.get('id') === 'HOME') && ( - <Link to='/timelines/home' className='drawer__tab' title={intl.formatMessage(messages.home_timeline)} aria-label={intl.formatMessage(messages.home_timeline)}><i role='img' className='fa fa-fw fa-home' /></Link> - )} - {!columns.some(column => column.get('id') === 'NOTIFICATIONS') && ( - <Link to='/notifications' className='drawer__tab' title={intl.formatMessage(messages.notifications)} aria-label={intl.formatMessage(messages.notifications)}><i role='img' className='fa fa-fw fa-bell' /></Link> - )} - {!columns.some(column => column.get('id') === 'COMMUNITY') && ( - <Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><i role='img' className='fa fa-fw fa-users' /></Link> - )} - {!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 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} - - <SearchContainer /> - - <div className='drawer__pager'> - <div className='drawer__inner scrollable optionally-scrollable' onFocus={this.onFocus}> - <NavigationContainer onClose={this.onBlur} /> - <ComposeFormContainer /> - </div> - - <Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}> - {({ x }) => - <div className='drawer__inner darker scrollable optionally-scrollable' style={{ transform: `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}> - <SearchResultsContainer /> - </div> - } - </Motion> - </div> - - </div> - ); - } - -} |