Improve compose form performance, upgrade JS dependencies. LightingBox
now allows to cycle through multiple images
This commit is contained in:
		
							parent
							
								
									3e9d794ea5
								
							
						
					
					
						commit
						2c50687279
					
				
					 14 changed files with 1251 additions and 713 deletions
				
			
		|  | @ -12,6 +12,9 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | |||
| import Toggle from 'react-toggle'; | ||||
| import Collapsable from '../../../components/collapsable'; | ||||
| import UnlistedToggleContainer from '../containers/unlisted_toggle_container'; | ||||
| import SpoilerToggleContainer from '../containers/spoiler_toggle_container'; | ||||
| import PrivateToggleContainer from '../containers/private_toggle_container'; | ||||
| import SensitiveToggleContainer from '../containers/sensitive_toggle_container'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   placeholder: { id: 'compose_form.placeholder', defaultMessage: 'What is on your mind?' }, | ||||
|  | @ -26,17 +29,15 @@ const ComposeForm = React.createClass({ | |||
|     text: React.PropTypes.string.isRequired, | ||||
|     suggestion_token: React.PropTypes.string, | ||||
|     suggestions: ImmutablePropTypes.list, | ||||
|     sensitive: React.PropTypes.bool, | ||||
|     spoiler: React.PropTypes.bool, | ||||
|     spoiler_text: React.PropTypes.string, | ||||
|     unlisted: React.PropTypes.bool, | ||||
|     private: React.PropTypes.bool, | ||||
|     unlisted: React.PropTypes.bool, | ||||
|     spoiler_text: React.PropTypes.string, | ||||
|     fileDropDate: React.PropTypes.instanceOf(Date), | ||||
|     focusDate: React.PropTypes.instanceOf(Date), | ||||
|     preselectDate: React.PropTypes.instanceOf(Date), | ||||
|     is_submitting: React.PropTypes.bool, | ||||
|     is_uploading: React.PropTypes.bool, | ||||
|     media_count: React.PropTypes.number, | ||||
|     me: React.PropTypes.number, | ||||
|     needsPrivacyWarning: React.PropTypes.bool, | ||||
|     mentionedDomains: React.PropTypes.array.isRequired, | ||||
|  | @ -45,10 +46,7 @@ const ComposeForm = React.createClass({ | |||
|     onClearSuggestions: React.PropTypes.func.isRequired, | ||||
|     onFetchSuggestions: React.PropTypes.func.isRequired, | ||||
|     onSuggestionSelected: React.PropTypes.func.isRequired, | ||||
|     onChangeSensitivity: React.PropTypes.func.isRequired, | ||||
|     onChangeSpoilerness: React.PropTypes.func.isRequired, | ||||
|     onChangeSpoilerText: React.PropTypes.func.isRequired, | ||||
|     onChangeVisibility: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
|  | @ -80,23 +78,10 @@ const ComposeForm = React.createClass({ | |||
|     this.props.onSuggestionSelected(tokenStart, token, value); | ||||
|   }, | ||||
| 
 | ||||
|   handleChangeSensitivity (e) { | ||||
|     this.props.onChangeSensitivity(e.target.checked); | ||||
|   }, | ||||
| 
 | ||||
|   handleChangeSpoilerness (e) { | ||||
|     this.props.onChangeSpoilerness(e.target.checked); | ||||
|     this.props.onChangeSpoilerText(''); | ||||
|   }, | ||||
| 
 | ||||
|   handleChangeSpoilerText (e) { | ||||
|     this.props.onChangeSpoilerText(e.target.value); | ||||
|   }, | ||||
| 
 | ||||
|   handleChangeVisibility (e) { | ||||
|     this.props.onChangeVisibility(e.target.checked); | ||||
|   }, | ||||
| 
 | ||||
|   componentDidUpdate (prevProps) { | ||||
|     if (this.props.focusDate !== prevProps.focusDate) { | ||||
|       // If replying to zero or one users, places the cursor at the end of the textbox. | ||||
|  | @ -172,24 +157,10 @@ const ComposeForm = React.createClass({ | |||
|           <UploadButtonContainer style={{ paddingTop: '4px' }} /> | ||||
|         </div> | ||||
| 
 | ||||
|         <label className='compose-form__label with-border' style={{ marginTop: '10px' }}> | ||||
|           <Toggle checked={this.props.spoiler} onChange={this.handleChangeSpoilerness} /> | ||||
|           <span className='compose-form__label__text'><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span> | ||||
|         </label> | ||||
| 
 | ||||
|         <label className='compose-form__label with-border'> | ||||
|           <Toggle checked={this.props.private} onChange={this.handleChangeVisibility} /> | ||||
|           <span className='compose-form__label__text'><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span> | ||||
|         </label> | ||||
| 
 | ||||
|         <SpoilerToggleContainer /> | ||||
|         <PrivateToggleContainer /> | ||||
|         <UnlistedToggleContainer /> | ||||
| 
 | ||||
|         <Collapsable isVisible={this.props.media_count > 0} fullHeight={39.5}> | ||||
|           <label className='compose-form__label'> | ||||
|             <Toggle checked={this.props.sensitive} onChange={this.handleChangeSensitivity} /> | ||||
|             <span className='compose-form__label__text'><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span> | ||||
|           </label> | ||||
|         </Collapsable> | ||||
|         <SensitiveToggleContainer /> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  |  | |||
|  | @ -0,0 +1,27 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import Toggle from 'react-toggle'; | ||||
| 
 | ||||
| const PrivateToggle = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     isPrivate: React.PropTypes.bool, | ||||
|     onChange: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     const { isPrivate, onChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <label className='compose-form__label with-border'> | ||||
|         <Toggle checked={isPrivate} onChange={onChange} /> | ||||
|         <span className='compose-form__label__text'><FormattedMessage id='compose_form.private' defaultMessage='Mark as private' /></span> | ||||
|       </label> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default PrivateToggle; | ||||
|  | @ -0,0 +1,31 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import Toggle from 'react-toggle'; | ||||
| import Collapsable from '../../../components/collapsable'; | ||||
| 
 | ||||
| const SensitiveToggle = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     hasMedia: React.PropTypes.bool, | ||||
|     isSensitive: React.PropTypes.bool, | ||||
|     onChange: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     const { hasMedia, isSensitive, onChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <Collapsable isVisible={hasMedia} fullHeight={39.5}> | ||||
|         <label className='compose-form__label'> | ||||
|           <Toggle checked={isSensitive} onChange={onChange} /> | ||||
|           <span className='compose-form__label__text'><FormattedMessage id='compose_form.sensitive' defaultMessage='Mark media as sensitive' /></span> | ||||
|         </label> | ||||
|       </Collapsable> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default SensitiveToggle; | ||||
|  | @ -0,0 +1,27 @@ | |||
| import PureRenderMixin from 'react-addons-pure-render-mixin'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import Toggle from 'react-toggle'; | ||||
| 
 | ||||
| const SpoilerToggle = React.createClass({ | ||||
| 
 | ||||
|   propTypes: { | ||||
|     isSpoiler: React.PropTypes.bool, | ||||
|     onChange: React.PropTypes.func.isRequired | ||||
|   }, | ||||
| 
 | ||||
|   mixins: [PureRenderMixin], | ||||
| 
 | ||||
|   render () { | ||||
|     const { isSpoiler, onChange } = this.props; | ||||
| 
 | ||||
|     return ( | ||||
|       <label className='compose-form__label with-border' style={{ marginTop: '10px' }}> | ||||
|         <Toggle checked={isSpoiler} onChange={onChange} /> | ||||
|         <span className='compose-form__label__text'><FormattedMessage id='compose_form.spoiler' defaultMessage='Hide text behind warning' /></span> | ||||
|       </label> | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default SpoilerToggle; | ||||
|  | @ -1,26 +1,29 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import ComposeForm from '../components/compose_form'; | ||||
| import { createSelector } from 'reselect'; | ||||
| import { | ||||
|   changeCompose, | ||||
|   submitCompose, | ||||
|   clearComposeSuggestions, | ||||
|   fetchComposeSuggestions, | ||||
|   selectComposeSuggestion, | ||||
|   changeComposeSensitivity, | ||||
|   changeComposeSpoilerness, | ||||
|   changeComposeSpoilerText, | ||||
|   changeComposeVisibility, | ||||
|   changeComposeListability | ||||
| } from '../../../actions/compose'; | ||||
| 
 | ||||
| const getMentionedUsernames = createSelector(state => state.getIn(['compose', 'text']), text => text.match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig)); | ||||
| 
 | ||||
| const getMentionedDomains = createSelector(getMentionedUsernames, mentionedUsernamesWithDomains => { | ||||
|   return mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : []; | ||||
| }); | ||||
| 
 | ||||
| const mapStateToProps = (state, props) => { | ||||
|   const mentionedUsernamesWithDomains = state.getIn(['compose', 'text']).match(/(?:^|[^\/\w])@([a-z0-9_]+@[a-z0-9\.\-]+)/ig); | ||||
|   const mentionedUsernames = getMentionedUsernames(state); | ||||
|   const mentionedUsernamesWithDomains = getMentionedDomains(state); | ||||
| 
 | ||||
|   return { | ||||
|     text: state.getIn(['compose', 'text']), | ||||
|     suggestion_token: state.getIn(['compose', 'suggestion_token']), | ||||
|     suggestions: state.getIn(['compose', 'suggestions']), | ||||
|     sensitive: state.getIn(['compose', 'sensitive']), | ||||
|     spoiler: state.getIn(['compose', 'spoiler']), | ||||
|     spoiler_text: state.getIn(['compose', 'spoiler_text']), | ||||
|     unlisted: state.getIn(['compose', 'unlisted'], ), | ||||
|  | @ -30,10 +33,9 @@ const mapStateToProps = (state, props) => { | |||
|     preselectDate: state.getIn(['compose', 'preselectDate']), | ||||
|     is_submitting: state.getIn(['compose', 'is_submitting']), | ||||
|     is_uploading: state.getIn(['compose', 'is_uploading']), | ||||
|     media_count: state.getIn(['compose', 'media_attachments']).size, | ||||
|     me: state.getIn(['compose', 'me']), | ||||
|     needsPrivacyWarning: state.getIn(['compose', 'private']) && mentionedUsernamesWithDomains !== null, | ||||
|     mentionedDomains: mentionedUsernamesWithDomains !== null ? [...new Set(mentionedUsernamesWithDomains.map(item => item.split('@')[2]))] : [] | ||||
|     needsPrivacyWarning: state.getIn(['compose', 'private']) && mentionedUsernames !== null, | ||||
|     mentionedDomains: mentionedUsernamesWithDomains | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
|  | @ -59,22 +61,10 @@ const mapDispatchToProps = (dispatch) => ({ | |||
|     dispatch(selectComposeSuggestion(position, token, accountId)); | ||||
|   }, | ||||
| 
 | ||||
|   onChangeSensitivity (checked) { | ||||
|     dispatch(changeComposeSensitivity(checked)); | ||||
|   }, | ||||
| 
 | ||||
|   onChangeSpoilerness (checked) { | ||||
|     dispatch(changeComposeSpoilerness(checked)); | ||||
|   }, | ||||
| 
 | ||||
|   onChangeSpoilerText (checked) { | ||||
|     dispatch(changeComposeSpoilerText(checked)); | ||||
|   }, | ||||
| 
 | ||||
|   onChangeVisibility (checked) { | ||||
|     dispatch(changeComposeVisibility(checked)); | ||||
|   }, | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(ComposeForm); | ||||
|  |  | |||
|  | @ -0,0 +1,17 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import PrivateToggle from '../components/private_toggle'; | ||||
| import { changeComposeVisibility } from '../../../actions/compose'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   isPrivate: state.getIn(['compose', 'private']) | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
| 
 | ||||
|   onChange (e) { | ||||
|     dispatch(changeComposeVisibility(e.target.checked)); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(PrivateToggle); | ||||
|  | @ -0,0 +1,18 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import SensitiveToggle from '../components/sensitive_toggle'; | ||||
| import { changeComposeSensitivity } from '../../../actions/compose'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   hasMedia: state.getIn(['compose', 'media_attachments']).size > 0, | ||||
|   isSensitive: state.getIn(['compose', 'sensitive']) | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
| 
 | ||||
|   onChange (e) { | ||||
|     dispatch(changeComposeSensitivity(e.target.checked)); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(SensitiveToggle); | ||||
|  | @ -0,0 +1,17 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import SpoilerToggle from '../components/spoiler_toggle'; | ||||
| import { changeComposeSpoilerness } from '../../../actions/compose'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   isSpoiler: state.getIn(['compose', 'spoiler']) | ||||
| }); | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
| 
 | ||||
|   onChange (e) { | ||||
|     dispatch(changeComposeSpoilerness(e.target.checked)); | ||||
|   } | ||||
| 
 | ||||
| }); | ||||
| 
 | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(SpoilerToggle); | ||||
|  | @ -132,18 +132,13 @@ const Modal = React.createClass({ | |||
|     } | ||||
| 
 | ||||
|     const url = media.get(index).get('url'); | ||||
|     const hasLeft  = index > 0; | ||||
|     const hasRight = index + 1 < media.size; | ||||
| 
 | ||||
|     let leftNav, rightNav; | ||||
| 
 | ||||
|     leftNav = rightNav = ''; | ||||
| 
 | ||||
|     if (hasLeft) { | ||||
|     if (media.size > 1) { | ||||
|       leftNav  = <div style={leftNavStyle} className='modal-container--nav' onClick={this.handlePrevClick}><i className='fa fa-fw fa-chevron-left' /></div>; | ||||
|     } | ||||
| 
 | ||||
|     if (hasRight) { | ||||
|       rightNav = <div style={rightNavStyle} className='modal-container--nav' onClick={this.handleNextClick}><i className='fa fa-fw fa-chevron-right' /></div>; | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -116,7 +116,10 @@ export default function compose(state = initialState, action) { | |||
|   case COMPOSE_SENSITIVITY_CHANGE: | ||||
|     return state.set('sensitive', action.checked); | ||||
|   case COMPOSE_SPOILERNESS_CHANGE: | ||||
|     return (action.checked ? state : state.set('spoiler_text', '')).set('spoiler', action.checked); | ||||
|     return state.withMutations(map => { | ||||
|       map.set('spoiler_text', ''); | ||||
|       map.set('spoiler', action.checked); | ||||
|     }); | ||||
|   case COMPOSE_SPOILER_TEXT_CHANGE: | ||||
|     return state.set('spoiler_text', action.text); | ||||
|   case COMPOSE_VISIBILITY_CHANGE: | ||||
|  |  | |||
|  | @ -23,9 +23,9 @@ export default function modal(state = initialState, action) { | |||
|   case MODAL_CLOSE: | ||||
|     return state.set('open', false); | ||||
|   case MODAL_INDEX_DECREASE: | ||||
|     return state.update('index', index => Math.max(index - 1, 0)); | ||||
|     return state.update('index', index => (index - 1) % state.get('media').size); | ||||
|   case MODAL_INDEX_INCREASE: | ||||
|     return state.update('index', index => Math.min(index + 1, state.get('media').size - 1)); | ||||
|     return state.update('index', index => (index + 1) % state.get('media').size); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
|  |  | |||
|  | @ -1,4 +1,4 @@ | |||
| import { createSelector } from 'reselect' | ||||
| import { createSelector } from 'reselect'; | ||||
| import Immutable from 'immutable'; | ||||
| 
 | ||||
| const getStatuses = state => state.get('statuses'); | ||||
|  |  | |||
							
								
								
									
										54
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										54
									
								
								package.json
									
									
									
									
									
								
							|  | @ -6,51 +6,51 @@ | |||
|     "start": "babel-node ./streaming/index.js --presets es2015,stage-2" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@kadira/storybook": "^2.24.0", | ||||
|     "axios": "^0.14.0", | ||||
|     "babel-cli": "^6.22.2", | ||||
|     "@kadira/storybook": "^2.35.3", | ||||
|     "axios": "^0.15.3", | ||||
|     "babel-cli": "^6.23.0", | ||||
|     "babel-plugin-react-transform": "^2.0.2", | ||||
|     "babel-plugin-transform-decorators-legacy": "^1.3.4", | ||||
|     "babel-plugin-transform-object-rest-spread": "^6.8.0", | ||||
|     "babel-plugin-transform-object-rest-spread": "^6.23.0", | ||||
|     "babel-preset-es2015": "^6.22.0", | ||||
|     "babel-preset-react": "^6.11.1", | ||||
|     "babel-preset-stage-2": "^6.22.0", | ||||
|     "babelify": "^7.3.0", | ||||
|     "browserify": "^13.1.0", | ||||
|     "browserify": "^14.1.0", | ||||
|     "browserify-incremental": "^3.1.1", | ||||
|     "bufferutil": "^2.0.0", | ||||
|     "bufferutil": "^2.0.1", | ||||
|     "chai": "^3.5.0", | ||||
|     "chai-enzyme": "^0.5.2", | ||||
|     "css-loader": "^0.26.1", | ||||
|     "chai-enzyme": "^0.6.1", | ||||
|     "css-loader": "^0.26.2", | ||||
|     "dotenv": "^4.0.0", | ||||
|     "emojione": "latest", | ||||
|     "enzyme": "^2.4.1", | ||||
|     "enzyme": "^2.7.1", | ||||
|     "es6-promise": "^3.2.1", | ||||
|     "eventsource": "^0.2.1", | ||||
|     "express": "^4.14.1", | ||||
|     "http-link-header": "^0.5.0", | ||||
|     "immutable": "^3.8.1", | ||||
|     "intl": "^1.2.5", | ||||
|     "jsdom": "^9.6.0", | ||||
|     "mocha": "^3.1.1", | ||||
|     "node-sass": "^4.0.0", | ||||
|     "jsdom": "^9.11.0", | ||||
|     "mocha": "^3.2.0", | ||||
|     "node-sass": "^4.5.0", | ||||
|     "npmlog": "^4.0.2", | ||||
|     "pg": "^6.1.2", | ||||
|     "react": "^15.3.2", | ||||
|     "react-addons-perf": "^15.3.2", | ||||
|     "react-addons-pure-render-mixin": "^15.3.1", | ||||
|     "react-addons-test-utils": "^15.3.2", | ||||
|     "react": "^15.4.2", | ||||
|     "react-addons-perf": "^15.4.2", | ||||
|     "react-addons-pure-render-mixin": "^15.4.2", | ||||
|     "react-addons-test-utils": "^15.4.2", | ||||
|     "react-autosuggest": "^7.0.1", | ||||
|     "react-decoration": "^1.4.0", | ||||
|     "react-dom": "^15.3.0", | ||||
|     "react-dom": "^15.4.2", | ||||
|     "react-imageloader": "^2.1.0", | ||||
|     "react-immutable-proptypes": "^2.1.0", | ||||
|     "react-intl": "^2.1.5", | ||||
|     "react-motion": "^0.4.5", | ||||
|     "react-notification": "^6.4.0", | ||||
|     "react-notification": "^6.6.0", | ||||
|     "react-proxy": "^1.1.8", | ||||
|     "react-redux": "^5.0.1", | ||||
|     "react-redux-loading-bar": "^2.4.1", | ||||
|     "react-redux": "^5.0.3", | ||||
|     "react-redux-loading-bar": "2.4.1", | ||||
|     "react-router": "^2.8.0", | ||||
|     "react-router-scroll": "^0.3.2", | ||||
|     "react-simple-dropdown": "^1.1.4", | ||||
|  | @ -58,17 +58,17 @@ | |||
|     "react-toggle": "^2.1.1", | ||||
|     "redis": "^2.6.5", | ||||
|     "redux": "^3.6.0", | ||||
|     "redux-immutable": "^3.0.8", | ||||
|     "redux-immutable": "^3.1.0", | ||||
|     "redux-sounds": "^1.1.1", | ||||
|     "redux-thunk": "^2.1.0", | ||||
|     "redux-thunk": "^2.2.0", | ||||
|     "reselect": "^2.5.4", | ||||
|     "sass-loader": "^4.0.2", | ||||
|     "sass-loader": "^6.0.2", | ||||
|     "sinon": "^1.17.6", | ||||
|     "style-loader": "^0.13.1", | ||||
|     "utf-8-validate": "^3.0.0", | ||||
|     "style-loader": "^0.13.2", | ||||
|     "utf-8-validate": "^3.0.1", | ||||
|     "uuid": "^3.0.1", | ||||
|     "webpack": "^1.14.0", | ||||
|     "webpack": "^2.2.1", | ||||
|     "websocket.js": "^0.1.7", | ||||
|     "ws": "^2.0.2" | ||||
|     "ws": "^2.1.0" | ||||
|   } | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue