Merge pull request #1682 from ClearlyClaire/glitch-soc/fixes/dropdowns-modals
Refactor and fix dropdown/action dialog
This commit is contained in:
		
						commit
						77fec39bee
					
				
					 7 changed files with 174 additions and 214 deletions
				
			
		|  | @ -116,7 +116,7 @@ class DropdownMenu extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|     if (typeof action === 'function') { |     if (typeof action === 'function') { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       action(); |       action(e); | ||||||
|     } else if (to) { |     } else if (to) { | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       this.context.router.history.push(to); |       this.context.router.history.push(to); | ||||||
|  | @ -128,11 +128,11 @@ class DropdownMenu extends React.PureComponent { | ||||||
|       return <li key={`sep-${i}`} className='dropdown-menu__separator' />; |       return <li key={`sep-${i}`} className='dropdown-menu__separator' />; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const { text, href = '#' } = option; |     const { text, href = '#', target = '_blank', method } = option; | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <li className='dropdown-menu__item' key={`${text}-${i}`}> |       <li className='dropdown-menu__item' key={`${text}-${i}`}> | ||||||
|         <a href={href} target='_blank' rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}> |         <a href={href} target={target} data-method={method} rel='noopener noreferrer' role='button' tabIndex='0' ref={i === 0 ? this.setFocusRef : null} onClick={this.handleClick} onKeyPress={this.handleItemKeyPress} data-index={i}> | ||||||
|           {text} |           {text} | ||||||
|         </a> |         </a> | ||||||
|       </li> |       </li> | ||||||
|  | @ -149,7 +149,7 @@ class DropdownMenu extends React.PureComponent { | ||||||
|           // It should not be transformed when mounting because the resulting
 |           // It should not be transformed when mounting because the resulting
 | ||||||
|           // size will be used to determine the coordinate of the menu by
 |           // size will be used to determine the coordinate of the menu by
 | ||||||
|           // react-overlays
 |           // react-overlays
 | ||||||
|           <div className='dropdown-menu' style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}> |           <div className={`dropdown-menu ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} ref={this.setRef}> | ||||||
|             <div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} /> |             <div className={`dropdown-menu__arrow ${placement}`} style={{ left: arrowOffsetLeft, top: arrowOffsetTop }} /> | ||||||
| 
 | 
 | ||||||
|             <ul> |             <ul> | ||||||
|  | @ -236,7 +236,8 @@ export default class Dropdown extends React.PureComponent { | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleItemClick = (i, e) => { |   handleItemClick = e => { | ||||||
|  |     const i = Number(e.currentTarget.getAttribute('data-index')); | ||||||
|     const { action, to } = this.props.items[i]; |     const { action, to } = this.props.items[i]; | ||||||
| 
 | 
 | ||||||
|     this.handleClose(); |     this.handleClose(); | ||||||
|  |  | ||||||
|  | @ -14,15 +14,11 @@ const mapDispatchToProps = (dispatch, { status, items, scrollKey }) => ({ | ||||||
|   onOpen(id, onItemClick, dropdownPlacement, keyboard) { |   onOpen(id, onItemClick, dropdownPlacement, keyboard) { | ||||||
|     dispatch(isUserTouching() ? openModal('ACTIONS', { |     dispatch(isUserTouching() ? openModal('ACTIONS', { | ||||||
|       status, |       status, | ||||||
|       actions: items.map( |       actions: items, | ||||||
|         (item, i) => item ? { |       onClick: onItemClick, | ||||||
|           ...item, |  | ||||||
|           name: `${item.text}-${i}`, |  | ||||||
|           onClick: item.action ? ((e) => { return onItemClick(i, e) }) : null, |  | ||||||
|         } : null |  | ||||||
|       ), |  | ||||||
|     }) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey)); |     }) : openDropdownMenu(id, dropdownPlacement, keyboard, scrollKey)); | ||||||
|   }, |   }, | ||||||
|  | 
 | ||||||
|   onClose(id) { |   onClose(id) { | ||||||
|     dispatch(closeModal('ACTIONS')); |     dispatch(closeModal('ACTIONS')); | ||||||
|     dispatch(closeDropdownMenu(id)); |     dispatch(closeDropdownMenu(id)); | ||||||
|  |  | ||||||
|  | @ -21,22 +21,25 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
|     icon: PropTypes.string, |     icon: PropTypes.string, | ||||||
|     items: PropTypes.arrayOf(PropTypes.shape({ |     items: PropTypes.arrayOf(PropTypes.shape({ | ||||||
|       icon: PropTypes.string, |       icon: PropTypes.string, | ||||||
|       meta: PropTypes.node, |       meta: PropTypes.string, | ||||||
|       name: PropTypes.string.isRequired, |       name: PropTypes.string.isRequired, | ||||||
|       on: PropTypes.bool, |       text: PropTypes.string, | ||||||
|       text: PropTypes.node, |  | ||||||
|     })).isRequired, |     })).isRequired, | ||||||
|     onModalOpen: PropTypes.func, |     onModalOpen: PropTypes.func, | ||||||
|     onModalClose: PropTypes.func, |     onModalClose: PropTypes.func, | ||||||
|     title: PropTypes.string, |     title: PropTypes.string, | ||||||
|     value: PropTypes.string, |     value: PropTypes.string, | ||||||
|     onChange: PropTypes.func, |     onChange: PropTypes.func, | ||||||
|     noModal: PropTypes.bool, |  | ||||||
|     container: PropTypes.func, |     container: PropTypes.func, | ||||||
|  |     renderItemContents: PropTypes.func, | ||||||
|  |     closeOnChange: PropTypes.bool, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   static defaultProps = { | ||||||
|  |     closeOnChange: true, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|     needsModalUpdate: false, |  | ||||||
|     open: false, |     open: false, | ||||||
|     openedViaKeyboard: undefined, |     openedViaKeyboard: undefined, | ||||||
|     placement: 'bottom', |     placement: 'bottom', | ||||||
|  | @ -44,10 +47,10 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|   //  Toggles opening and closing the dropdown.
 |   //  Toggles opening and closing the dropdown.
 | ||||||
|   handleToggle = ({ target, type }) => { |   handleToggle = ({ target, type }) => { | ||||||
|     const { onModalOpen, noModal } = this.props; |     const { onModalOpen } = this.props; | ||||||
|     const { open } = this.state; |     const { open } = this.state; | ||||||
| 
 | 
 | ||||||
|     if (!noModal && isUserTouching()) { |     if (isUserTouching()) { | ||||||
|       if (this.state.open) { |       if (this.state.open) { | ||||||
|         this.props.onModalClose(); |         this.props.onModalClose(); | ||||||
|       } else { |       } else { | ||||||
|  | @ -107,9 +110,25 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
|     this.setState({ open: false }); |     this.setState({ open: false }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   handleItemClick = (e) => { | ||||||
|  |     const { | ||||||
|  |       items, | ||||||
|  |       onChange, | ||||||
|  |       onModalClose, | ||||||
|  |       closeOnChange, | ||||||
|  |     } = this.props; | ||||||
|  | 
 | ||||||
|  |     const i = Number(e.currentTarget.getAttribute('data-index')); | ||||||
|  | 
 | ||||||
|  |     const { name } = items[i]; | ||||||
|  | 
 | ||||||
|  |     e.preventDefault();  //  Prevents focus from changing
 | ||||||
|  |     if (closeOnChange) onModalClose(); | ||||||
|  |     onChange(name); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   //  Creates an action modal object.
 |   //  Creates an action modal object.
 | ||||||
|   handleMakeModal = () => { |   handleMakeModal = () => { | ||||||
|     const component = this; |  | ||||||
|     const { |     const { | ||||||
|       items, |       items, | ||||||
|       onChange, |       onChange, | ||||||
|  | @ -125,6 +144,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
| 
 | 
 | ||||||
|     //  The object.
 |     //  The object.
 | ||||||
|     return { |     return { | ||||||
|  |       renderItemContents: this.props.renderItemContents, | ||||||
|  |       onClick: this.handleItemClick, | ||||||
|       actions: items.map( |       actions: items.map( | ||||||
|         ({ |         ({ | ||||||
|           name, |           name, | ||||||
|  | @ -133,48 +154,11 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
|           ...rest, |           ...rest, | ||||||
|           active: value && name === value, |           active: value && name === value, | ||||||
|           name, |           name, | ||||||
|           onClick (e) { |  | ||||||
|             e.preventDefault();  //  Prevents focus from changing
 |  | ||||||
|             onModalClose(); |  | ||||||
|             onChange(name); |  | ||||||
|           }, |  | ||||||
|           onPassiveClick (e) { |  | ||||||
|             e.preventDefault();  //  Prevents focus from changing
 |  | ||||||
|             onChange(name); |  | ||||||
|             component.setState({ needsModalUpdate: true }); |  | ||||||
|           }, |  | ||||||
|         }) |         }) | ||||||
|       ), |       ), | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   //  If our modal is open and our props update, we need to also update
 |  | ||||||
|   //  the modal.
 |  | ||||||
|   handleUpdate = () => { |  | ||||||
|     const { onModalOpen } = this.props; |  | ||||||
|     const { needsModalUpdate } = this.state; |  | ||||||
| 
 |  | ||||||
|     //  Gets our modal object.
 |  | ||||||
|     const modal = this.handleMakeModal(); |  | ||||||
| 
 |  | ||||||
|     //  Reopens the modal with the new object.
 |  | ||||||
|     if (needsModalUpdate && modal && onModalOpen) { |  | ||||||
|       onModalOpen(modal); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   //  Updates our modal as necessary.
 |  | ||||||
|   componentDidUpdate (prevProps) { |  | ||||||
|     const { items } = this.props; |  | ||||||
|     const { needsModalUpdate } = this.state; |  | ||||||
|     if (needsModalUpdate && items.find( |  | ||||||
|       (item, i) => item.on !== prevProps.items[i].on |  | ||||||
|     )) { |  | ||||||
|       this.handleUpdate(); |  | ||||||
|       this.setState({ needsModalUpdate: false }); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   //  Rendering.
 |   //  Rendering.
 | ||||||
|   render () { |   render () { | ||||||
|     const { |     const { | ||||||
|  | @ -186,6 +170,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
|       onChange, |       onChange, | ||||||
|       value, |       value, | ||||||
|       container, |       container, | ||||||
|  |       renderItemContents, | ||||||
|  |       closeOnChange, | ||||||
|     } = this.props; |     } = this.props; | ||||||
|     const { open, placement } = this.state; |     const { open, placement } = this.state; | ||||||
|     const computedClass = classNames('composer--options--dropdown', { |     const computedClass = classNames('composer--options--dropdown', { | ||||||
|  | @ -226,10 +212,12 @@ export default class ComposerOptionsDropdown extends React.PureComponent { | ||||||
|         > |         > | ||||||
|           <DropdownMenu |           <DropdownMenu | ||||||
|             items={items} |             items={items} | ||||||
|  |             renderItemContents={renderItemContents} | ||||||
|             onChange={onChange} |             onChange={onChange} | ||||||
|             onClose={this.handleClose} |             onClose={this.handleClose} | ||||||
|             value={value} |             value={value} | ||||||
|             openedViaKeyboard={this.state.openedViaKeyboard} |             openedViaKeyboard={this.state.openedViaKeyboard} | ||||||
|  |             closeOnChange={closeOnChange} | ||||||
|           /> |           /> | ||||||
|         </Overlay> |         </Overlay> | ||||||
|       </div> |       </div> | ||||||
|  |  | ||||||
|  | @ -2,7 +2,6 @@ | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import spring from 'react-motion/lib/spring'; | import spring from 'react-motion/lib/spring'; | ||||||
| import Toggle from 'react-toggle'; |  | ||||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| 
 | 
 | ||||||
|  | @ -28,18 +27,20 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|       icon: PropTypes.string, |       icon: PropTypes.string, | ||||||
|       meta: PropTypes.node, |       meta: PropTypes.node, | ||||||
|       name: PropTypes.string.isRequired, |       name: PropTypes.string.isRequired, | ||||||
|       on: PropTypes.bool, |  | ||||||
|       text: PropTypes.node, |       text: PropTypes.node, | ||||||
|     })), |     })), | ||||||
|     onChange: PropTypes.func.isRequired, |     onChange: PropTypes.func.isRequired, | ||||||
|     onClose: PropTypes.func.isRequired, |     onClose: PropTypes.func.isRequired, | ||||||
|     style: PropTypes.object, |     style: PropTypes.object, | ||||||
|     value: PropTypes.string, |     value: PropTypes.string, | ||||||
|  |     renderItemContents: PropTypes.func, | ||||||
|     openedViaKeyboard: PropTypes.bool, |     openedViaKeyboard: PropTypes.bool, | ||||||
|  |     closeOnChange: PropTypes.bool, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   static defaultProps = { |   static defaultProps = { | ||||||
|     style: {}, |     style: {}, | ||||||
|  |     closeOnChange: true, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   state = { |   state = { | ||||||
|  | @ -77,16 +78,19 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|     document.removeEventListener('touchend', this.handleDocumentClick, withPassive); |     document.removeEventListener('touchend', this.handleDocumentClick, withPassive); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleClick = (name, e) => { |   handleClick = (e) => { | ||||||
|  |     const i = Number(e.currentTarget.getAttribute('data-index')); | ||||||
|  | 
 | ||||||
|     const { |     const { | ||||||
|       onChange, |       onChange, | ||||||
|       onClose, |       onClose, | ||||||
|  |       closeOnChange, | ||||||
|       items, |       items, | ||||||
|     } = this.props; |     } = this.props; | ||||||
| 
 | 
 | ||||||
|     const { on } = this.props.items.find(item => item.name === name); |     const { name } = this.props.items[i]; | ||||||
|     e.preventDefault();  //  Prevents change in focus on click
 |     e.preventDefault();  //  Prevents change in focus on click
 | ||||||
|     if ((on === null || typeof on === 'undefined')) { |     if (closeOnChange) { | ||||||
|       onClose(); |       onClose(); | ||||||
|     } |     } | ||||||
|     onChange(name); |     onChange(name); | ||||||
|  | @ -101,11 +105,9 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   handleKeyDown = (name, e) => { |   handleKeyDown = (e) => { | ||||||
|  |     const index = Number(e.currentTarget.getAttribute('data-index')); | ||||||
|     const { items } = this.props; |     const { items } = this.props; | ||||||
|     const index = items.findIndex(item => { |  | ||||||
|       return (item.name === name); |  | ||||||
|     }); |  | ||||||
|     let element = null; |     let element = null; | ||||||
| 
 | 
 | ||||||
|     switch(e.key) { |     switch(e.key) { | ||||||
|  | @ -139,7 +141,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
| 
 | 
 | ||||||
|     if (element) { |     if (element) { | ||||||
|       element.focus(); |       element.focus(); | ||||||
|       this.handleChange(element.getAttribute('data-index')); |       this.handleChange(this.props.items[Number(element.getAttribute('data-index'))].name); | ||||||
|       e.preventDefault(); |       e.preventDefault(); | ||||||
|       e.stopPropagation(); |       e.stopPropagation(); | ||||||
|     } |     } | ||||||
|  | @ -149,44 +151,40 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|     this.focusedItem = c; |     this.focusedItem = c; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   renderItem = (item) => { |   renderItem = (item, i) => { | ||||||
|     const { name, icon, meta, on, text } = item; |     const { name, icon, meta, text } = item; | ||||||
| 
 | 
 | ||||||
|     const active = (name === (this.props.value || this.state.value)); |     const active = (name === (this.props.value || this.state.value)); | ||||||
| 
 | 
 | ||||||
|     const computedClass = classNames('composer--options--dropdown--content--item', { |     const computedClass = classNames('composer--options--dropdown--content--item', { active }); | ||||||
|       active, |  | ||||||
|       lengthy: meta, |  | ||||||
|       'toggled-off': !on && on !== null && typeof on !== 'undefined', |  | ||||||
|       'toggled-on': on, |  | ||||||
|       'with-icon': icon, |  | ||||||
|     }); |  | ||||||
| 
 | 
 | ||||||
|     let prefix = null; |     let contents = this.props.renderItemContents && this.props.renderItemContents(item, i); | ||||||
| 
 | 
 | ||||||
|     if (on !== null && typeof on !== 'undefined') { |     if (!contents) { | ||||||
|       prefix = <Toggle checked={on} onChange={this.handleClick.bind(this, name)} />; |       contents = ( | ||||||
|     } else if (icon) { |         <React.Fragment> | ||||||
|       prefix = <Icon className='icon' fixedWidth id={icon} /> |           {icon && <Icon className='icon' fixedWidth id={icon} />} | ||||||
|  | 
 | ||||||
|  |           <div className='content'> | ||||||
|  |             <strong>{text}</strong> | ||||||
|  |             {meta} | ||||||
|  |           </div> | ||||||
|  |         </React.Fragment> | ||||||
|  |       ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <div |       <div | ||||||
|         className={computedClass} |         className={computedClass} | ||||||
|         onClick={this.handleClick.bind(this, name)} |         onClick={this.handleClick} | ||||||
|         onKeyDown={this.handleKeyDown.bind(this, name)} |         onKeyDown={this.handleKeyDown} | ||||||
|         role='option' |         role='option' | ||||||
|         tabIndex='0' |         tabIndex='0' | ||||||
|         key={name} |         key={name} | ||||||
|         data-index={name} |         data-index={i} | ||||||
|         ref={active ? this.setFocusRef : null} |         ref={active ? this.setFocusRef : null} | ||||||
|       > |       > | ||||||
|         {prefix} |         {contents} | ||||||
| 
 |  | ||||||
|         <div className='content'> |  | ||||||
|           <strong>{text}</strong> |  | ||||||
|           {meta} |  | ||||||
|         </div> |  | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  | @ -229,7 +227,7 @@ export default class ComposerOptionsDropdownContent extends React.PureComponent | ||||||
|               transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, |               transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, | ||||||
|             }} |             }} | ||||||
|           > |           > | ||||||
|             {!!items && items.map(item => this.renderItem(item))} |             {!!items && items.map((item, i) => this.renderItem(item, i))} | ||||||
|           </div> |           </div> | ||||||
|         )} |         )} | ||||||
|       </Motion> |       </Motion> | ||||||
|  |  | ||||||
|  | @ -2,8 +2,10 @@ | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import spring from 'react-motion/lib/spring'; | import spring from 'react-motion/lib/spring'; | ||||||
|  | import Toggle from 'react-toggle'; | ||||||
|  | import { connect } from 'react-redux'; | ||||||
| 
 | 
 | ||||||
| //  Components.
 | //  Components.
 | ||||||
| import IconButton from 'flavours/glitch/components/icon_button'; | import IconButton from 'flavours/glitch/components/icon_button'; | ||||||
|  | @ -80,6 +82,36 @@ const messages = defineMessages({ | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | @connect((state, { name }) => ({ checked: state.getIn(['compose', 'advanced_options', name]) })) | ||||||
|  | class ToggleOption extends ImmutablePureComponent { | ||||||
|  | 
 | ||||||
|  |   static propTypes = { | ||||||
|  |     name: PropTypes.string.isRequired, | ||||||
|  |     checked: PropTypes.bool, | ||||||
|  |     onChangeAdvancedOption: PropTypes.func.isRequired, | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   handleChange = () => { | ||||||
|  |     this.props.onChangeAdvancedOption(this.props.name); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   render() { | ||||||
|  |     const { meta, text, checked } = this.props; | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |       <React.Fragment> | ||||||
|  |         <Toggle checked={checked} onChange={this.handleChange} /> | ||||||
|  | 
 | ||||||
|  |         <div className='content'> | ||||||
|  |           <strong>{text}</strong> | ||||||
|  |           {meta} | ||||||
|  |         </div> | ||||||
|  |       </React.Fragment> | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export default @injectIntl | export default @injectIntl | ||||||
| class ComposerOptions extends ImmutablePureComponent { | class ComposerOptions extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|  | @ -141,6 +173,13 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|     this.fileElement = fileElement; |     this.fileElement = fileElement; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   renderToggleItemContents = (item) => { | ||||||
|  |     const { onChangeAdvancedOption } = this.props; | ||||||
|  |     const { name, meta, text } = item; | ||||||
|  | 
 | ||||||
|  |     return <ToggleOption name={name} text={text} meta={meta} onChangeAdvancedOption={onChangeAdvancedOption} />; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   //  Rendering.
 |   //  Rendering.
 | ||||||
|   render () { |   render () { | ||||||
|     const { |     const { | ||||||
|  | @ -152,7 +191,6 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|       hasMedia, |       hasMedia, | ||||||
|       allowPoll, |       allowPoll, | ||||||
|       hasPoll, |       hasPoll, | ||||||
|       intl, |  | ||||||
|       onChangeAdvancedOption, |       onChangeAdvancedOption, | ||||||
|       onChangeContentType, |       onChangeContentType, | ||||||
|       onChangeVisibility, |       onChangeVisibility, | ||||||
|  | @ -164,23 +202,24 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|       resetFileKey, |       resetFileKey, | ||||||
|       spoiler, |       spoiler, | ||||||
|       showContentTypeChoice, |       showContentTypeChoice, | ||||||
|  |       intl: { formatMessage }, | ||||||
|     } = this.props; |     } = this.props; | ||||||
| 
 | 
 | ||||||
|     const contentTypeItems = { |     const contentTypeItems = { | ||||||
|       plain: { |       plain: { | ||||||
|         icon: 'file-text', |         icon: 'file-text', | ||||||
|         name: 'text/plain', |         name: 'text/plain', | ||||||
|         text: <FormattedMessage {...messages.plain} />, |         text: formatMessage(messages.plain), | ||||||
|       }, |       }, | ||||||
|       html: { |       html: { | ||||||
|         icon: 'code', |         icon: 'code', | ||||||
|         name: 'text/html', |         name: 'text/html', | ||||||
|         text: <FormattedMessage {...messages.html} />, |         text: formatMessage(messages.html), | ||||||
|       }, |       }, | ||||||
|       markdown: { |       markdown: { | ||||||
|         icon: 'arrow-circle-down', |         icon: 'arrow-circle-down', | ||||||
|         name: 'text/markdown', |         name: 'text/markdown', | ||||||
|         text: <FormattedMessage {...messages.markdown} />, |         text: formatMessage(messages.markdown), | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -204,18 +243,18 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|             { |             { | ||||||
|               icon: 'cloud-upload', |               icon: 'cloud-upload', | ||||||
|               name: 'upload', |               name: 'upload', | ||||||
|               text: <FormattedMessage {...messages.upload} />, |               text: formatMessage(messages.upload), | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               icon: 'paint-brush', |               icon: 'paint-brush', | ||||||
|               name: 'doodle', |               name: 'doodle', | ||||||
|               text: <FormattedMessage {...messages.doodle} />, |               text: formatMessage(messages.doodle), | ||||||
|             }, |             }, | ||||||
|           ]} |           ]} | ||||||
|           onChange={this.handleClickAttach} |           onChange={this.handleClickAttach} | ||||||
|           onModalClose={onModalClose} |           onModalClose={onModalClose} | ||||||
|           onModalOpen={onModalOpen} |           onModalOpen={onModalOpen} | ||||||
|           title={intl.formatMessage(messages.attach)} |           title={formatMessage(messages.attach)} | ||||||
|         /> |         /> | ||||||
|         {!!pollLimits && ( |         {!!pollLimits && ( | ||||||
|           <IconButton |           <IconButton | ||||||
|  | @ -229,7 +268,7 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|               height: null, |               height: null, | ||||||
|               lineHeight: null, |               lineHeight: null, | ||||||
|             }} |             }} | ||||||
|             title={intl.formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)} |             title={formatMessage(hasPoll ? messages.remove_poll : messages.add_poll)} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|         <hr /> |         <hr /> | ||||||
|  | @ -252,7 +291,7 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|             onChange={onChangeContentType} |             onChange={onChangeContentType} | ||||||
|             onModalClose={onModalClose} |             onModalClose={onModalClose} | ||||||
|             onModalOpen={onModalOpen} |             onModalOpen={onModalOpen} | ||||||
|             title={intl.formatMessage(messages.content_type)} |             title={formatMessage(messages.content_type)} | ||||||
|             value={contentType} |             value={contentType} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|  | @ -262,7 +301,7 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|             ariaControls='glitch.composer.spoiler.input' |             ariaControls='glitch.composer.spoiler.input' | ||||||
|             label='CW' |             label='CW' | ||||||
|             onClick={onToggleSpoiler} |             onClick={onToggleSpoiler} | ||||||
|             title={intl.formatMessage(messages.spoiler)} |             title={formatMessage(messages.spoiler)} | ||||||
|           /> |           /> | ||||||
|         )} |         )} | ||||||
|         <Dropdown |         <Dropdown | ||||||
|  | @ -271,22 +310,22 @@ class ComposerOptions extends ImmutablePureComponent { | ||||||
|           icon='ellipsis-h' |           icon='ellipsis-h' | ||||||
|           items={advancedOptions ? [ |           items={advancedOptions ? [ | ||||||
|             { |             { | ||||||
|               meta: <FormattedMessage {...messages.local_only_long} />, |               meta: formatMessage(messages.local_only_long), | ||||||
|               name: 'do_not_federate', |               name: 'do_not_federate', | ||||||
|               on: advancedOptions.get('do_not_federate'), |               text: formatMessage(messages.local_only_short), | ||||||
|               text: <FormattedMessage {...messages.local_only_short} />, |  | ||||||
|             }, |             }, | ||||||
|             { |             { | ||||||
|               meta: <FormattedMessage {...messages.threaded_mode_long} />, |               meta: formatMessage(messages.threaded_mode_long), | ||||||
|               name: 'threaded_mode', |               name: 'threaded_mode', | ||||||
|               on: advancedOptions.get('threaded_mode'), |               text: formatMessage(messages.threaded_mode_short), | ||||||
|               text: <FormattedMessage {...messages.threaded_mode_short} />, |  | ||||||
|             }, |             }, | ||||||
|           ] : null} |           ] : null} | ||||||
|           onChange={onChangeAdvancedOption} |           onChange={onChangeAdvancedOption} | ||||||
|  |           renderItemContents={this.renderToggleItemContents} | ||||||
|           onModalClose={onModalClose} |           onModalClose={onModalClose} | ||||||
|           onModalOpen={onModalOpen} |           onModalOpen={onModalOpen} | ||||||
|           title={intl.formatMessage(messages.advanced_options_icon_title)} |           title={formatMessage(messages.advanced_options_icon_title)} | ||||||
|  |           closeOnChange={false} | ||||||
|         /> |         /> | ||||||
|       </div> |       </div> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -1,46 +1,19 @@ | ||||||
| import PropTypes from 'prop-types'; | import PropTypes from 'prop-types'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||||
| import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; | import { defineMessages, injectIntl } from 'react-intl'; | ||||||
| import Dropdown from './dropdown'; | import Dropdown from './dropdown'; | ||||||
| 
 | 
 | ||||||
| const messages = defineMessages({ | const messages = defineMessages({ | ||||||
|   change_privacy: { |   public_short: { id: 'privacy.public.short', defaultMessage: 'Public' }, | ||||||
|     defaultMessage: 'Adjust status privacy', |   public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all, shown in public timelines' }, | ||||||
|     id: 'privacy.change', |   unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' }, | ||||||
|   }, |   unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' }, | ||||||
|   direct_long: { |   private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' }, | ||||||
|     defaultMessage: 'Visible for mentioned users only', |   private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' }, | ||||||
|     id: 'privacy.direct.long', |   direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, | ||||||
|   }, |   direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' }, | ||||||
|   direct_short: { |   change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' }, | ||||||
|     defaultMessage: 'Direct', |  | ||||||
|     id: 'privacy.direct.short', |  | ||||||
|   }, |  | ||||||
|   private_long: { |  | ||||||
|     defaultMessage: 'Visible for followers only', |  | ||||||
|     id: 'privacy.private.long', |  | ||||||
|   }, |  | ||||||
|   private_short: { |  | ||||||
|     defaultMessage: 'Followers-only', |  | ||||||
|     id: 'privacy.private.short', |  | ||||||
|   }, |  | ||||||
|   public_long: { |  | ||||||
|     defaultMessage: 'Visible for all, shown in public timelines', |  | ||||||
|     id: 'privacy.public.long', |  | ||||||
|   }, |  | ||||||
|   public_short: { |  | ||||||
|     defaultMessage: 'Public', |  | ||||||
|     id: 'privacy.public.short', |  | ||||||
|   }, |  | ||||||
|   unlisted_long: { |  | ||||||
|     defaultMessage: 'Visible for all, but not in public timelines', |  | ||||||
|     id: 'privacy.unlisted.long', |  | ||||||
|   }, |  | ||||||
|   unlisted_short: { |  | ||||||
|     defaultMessage: 'Unlisted', |  | ||||||
|     id: 'privacy.unlisted.short', |  | ||||||
|   }, |  | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| export default @injectIntl | export default @injectIntl | ||||||
|  | @ -53,40 +26,39 @@ class PrivacyDropdown extends React.PureComponent { | ||||||
|     value: PropTypes.string.isRequired, |     value: PropTypes.string.isRequired, | ||||||
|     onChange: PropTypes.func.isRequired, |     onChange: PropTypes.func.isRequired, | ||||||
|     noDirect: PropTypes.bool, |     noDirect: PropTypes.bool, | ||||||
|     noModal: PropTypes.bool, |  | ||||||
|     container: PropTypes.func, |     container: PropTypes.func, | ||||||
|     intl: PropTypes.object.isRequired, |     intl: PropTypes.object.isRequired, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   render () { |   render () { | ||||||
|     const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, noModal, container, intl } = this.props; |     const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props; | ||||||
| 
 | 
 | ||||||
|     //  We predefine our privacy items so that we can easily pick the
 |     //  We predefine our privacy items so that we can easily pick the
 | ||||||
|     //  dropdown icon later.
 |     //  dropdown icon later.
 | ||||||
|     const privacyItems = { |     const privacyItems = { | ||||||
|       direct: { |       direct: { | ||||||
|         icon: 'envelope', |         icon: 'envelope', | ||||||
|         meta: <FormattedMessage {...messages.direct_long} />, |         meta: formatMessage(messages.direct_long), | ||||||
|         name: 'direct', |         name: 'direct', | ||||||
|         text: <FormattedMessage {...messages.direct_short} />, |         text: formatMessage(messages.direct_short), | ||||||
|       }, |       }, | ||||||
|       private: { |       private: { | ||||||
|         icon: 'lock', |         icon: 'lock', | ||||||
|         meta: <FormattedMessage {...messages.private_long} />, |         meta: formatMessage(messages.private_long), | ||||||
|         name: 'private', |         name: 'private', | ||||||
|         text: <FormattedMessage {...messages.private_short} />, |         text: formatMessage(messages.private_short), | ||||||
|       }, |       }, | ||||||
|       public: { |       public: { | ||||||
|         icon: 'globe', |         icon: 'globe', | ||||||
|         meta: <FormattedMessage {...messages.public_long} />, |         meta: formatMessage(messages.public_long), | ||||||
|         name: 'public', |         name: 'public', | ||||||
|         text: <FormattedMessage {...messages.public_short} />, |         text: formatMessage(messages.public_short), | ||||||
|       }, |       }, | ||||||
|       unlisted: { |       unlisted: { | ||||||
|         icon: 'unlock', |         icon: 'unlock', | ||||||
|         meta: <FormattedMessage {...messages.unlisted_long} />, |         meta: formatMessage(messages.unlisted_long), | ||||||
|         name: 'unlisted', |         name: 'unlisted', | ||||||
|         text: <FormattedMessage {...messages.unlisted_short} />, |         text: formatMessage(messages.unlisted_short), | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -104,9 +76,8 @@ class PrivacyDropdown extends React.PureComponent { | ||||||
|         onChange={onChange} |         onChange={onChange} | ||||||
|         onModalClose={onModalClose} |         onModalClose={onModalClose} | ||||||
|         onModalOpen={onModalOpen} |         onModalOpen={onModalOpen} | ||||||
|         title={intl.formatMessage(messages.change_privacy)} |         title={formatMessage(messages.change_privacy)} | ||||||
|         container={container} |         container={container} | ||||||
|         noModal={noModal} |  | ||||||
|         value={value} |         value={value} | ||||||
|       /> |       /> | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|  | @ -7,24 +7,22 @@ import Avatar from 'flavours/glitch/components/avatar'; | ||||||
| import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; | import RelativeTimestamp from 'flavours/glitch/components/relative_timestamp'; | ||||||
| import DisplayName from 'flavours/glitch/components/display_name'; | import DisplayName from 'flavours/glitch/components/display_name'; | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
| import Icon from 'flavours/glitch/components/icon'; | import IconButton from 'flavours/glitch/components/icon_button'; | ||||||
| import Link from 'flavours/glitch/components/link'; |  | ||||||
| import Toggle from 'react-toggle'; |  | ||||||
| 
 | 
 | ||||||
| export default class ActionsModal extends ImmutablePureComponent { | export default class ActionsModal extends ImmutablePureComponent { | ||||||
| 
 | 
 | ||||||
|   static propTypes = { |   static propTypes = { | ||||||
|     status: ImmutablePropTypes.map, |     status: ImmutablePropTypes.map, | ||||||
|  |     onClick: PropTypes.func, | ||||||
|     actions: PropTypes.arrayOf(PropTypes.shape({ |     actions: PropTypes.arrayOf(PropTypes.shape({ | ||||||
|       active: PropTypes.bool, |       active: PropTypes.bool, | ||||||
|       href: PropTypes.string, |       href: PropTypes.string, | ||||||
|       icon: PropTypes.string, |       icon: PropTypes.string, | ||||||
|       meta: PropTypes.node, |       meta: PropTypes.string, | ||||||
|       name: PropTypes.string, |       name: PropTypes.string, | ||||||
|       on: PropTypes.bool, |       text: PropTypes.string, | ||||||
|       onPassiveClick: PropTypes.func, |  | ||||||
|       text: PropTypes.node, |  | ||||||
|     })), |     })), | ||||||
|  |     renderItemContents: PropTypes.func, | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   renderAction = (action, i) => { |   renderAction = (action, i) => { | ||||||
|  | @ -32,57 +30,26 @@ export default class ActionsModal extends ImmutablePureComponent { | ||||||
|       return <li key={`sep-${i}`} className='dropdown-menu__separator' />; |       return <li key={`sep-${i}`} className='dropdown-menu__separator' />; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     const { |     const { icon = null, text, meta = null, active = false, href = '#' } = action; | ||||||
|       active, |     let contents = this.props.renderItemContents && this.props.renderItemContents(action, i); | ||||||
|       href, | 
 | ||||||
|       icon, |     if (!contents) { | ||||||
|       meta, |       contents = ( | ||||||
|       name, |         <React.Fragment> | ||||||
|       on, |           {icon && <IconButton title={text} icon={icon} role='presentation' tabIndex='-1' inverted />} | ||||||
|       onClick, |           <div> | ||||||
|       onPassiveClick, |             <div className={classNames({ 'actions-modal__item-label': !!meta })}>{text}</div> | ||||||
|       text, |             <div>{meta}</div> | ||||||
|     } = action; |           </div> | ||||||
|  |         </React.Fragment> | ||||||
|  |       ); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|       <li key={name || i}> |       <li key={`${text}-${i}`}> | ||||||
|         <Link |         <a href={href} target='_blank' rel='noopener noreferrer' onClick={this.props.onClick} data-index={i} className={classNames('link', { active })}> | ||||||
|           className={classNames('link', { active })} |           {contents} | ||||||
|           href={href} |         </a> | ||||||
|           onClick={on !== null && typeof on !== 'undefined' && onPassiveClick || onClick} |  | ||||||
|           role={onClick ? 'button' : null} |  | ||||||
|         > |  | ||||||
|           {function () { |  | ||||||
| 
 |  | ||||||
|             //  We render a `<Toggle>` if we were provided an `on`
 |  | ||||||
|             //  property, and otherwise show an `<Icon>` if available.
 |  | ||||||
|             switch (true) { |  | ||||||
|             case on !== null && typeof on !== 'undefined': |  | ||||||
|               return ( |  | ||||||
|                 <Toggle |  | ||||||
|                   checked={on} |  | ||||||
|                   onChange={onPassiveClick || onClick} |  | ||||||
|                 /> |  | ||||||
|               ); |  | ||||||
|             case !!icon: |  | ||||||
|               return ( |  | ||||||
|                 <Icon |  | ||||||
|                   className='icon' |  | ||||||
|                   fixedWidth |  | ||||||
|                   id={icon} |  | ||||||
|                 /> |  | ||||||
|               ); |  | ||||||
|             default: |  | ||||||
|               return null; |  | ||||||
|             } |  | ||||||
|           }()} |  | ||||||
|           {meta ? ( |  | ||||||
|             <div> |  | ||||||
|               <strong>{text}</strong> |  | ||||||
|               {meta} |  | ||||||
|             </div> |  | ||||||
|           ) : <div>{text}</div>} |  | ||||||
|         </Link> |  | ||||||
|       </li> |       </li> | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue