Put "Media Only" option in column settings instead of content area headline (#7801)
* Action/reducer for changing column settings takes a path and a value instead of a javascript object * Settings menu version and column headline version working simultaneously * remove column headline entirely * remove css for headlines that aren't possible now * Remove commented out code from unfruitful attempt at this feature * Give direct timeline its own column settings bc it doesn't have a media only option * Fix typo in public timeline code that was preventing per-column settings from working properly * Fix codeclimate issues * Missing semicolons * Use redux state to set onlyMedia, let that do the update instead of a callback. Consequently, unpinned setting works without history modification * Unused import
This commit is contained in:
		| @@ -40,12 +40,13 @@ export function moveColumn(uuid, direction) { | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export function changeColumnParams(uuid, params) { | ||||
| export function changeColumnParams(uuid, path, value) { | ||||
|   return dispatch => { | ||||
|     dispatch({ | ||||
|       type: COLUMN_PARAMS_CHANGE, | ||||
|       uuid, | ||||
|       params, | ||||
|       path, | ||||
|       value, | ||||
|     }); | ||||
|  | ||||
|     dispatch(saveSettings()); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import SettingText from '../../../components/setting_text'; | ||||
| import SettingToggle from '../../notifications/components/setting_toggle'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, | ||||
| @@ -16,6 +17,7 @@ export default class ColumnSettings extends React.PureComponent { | ||||
|     settings: ImmutablePropTypes.map.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|     columnId: PropTypes.string, | ||||
|   }; | ||||
|  | ||||
|   render () { | ||||
| @@ -23,6 +25,10 @@ export default class ColumnSettings extends React.PureComponent { | ||||
|  | ||||
|     return ( | ||||
|       <div> | ||||
|         <div className='column-settings__row'> | ||||
|           <SettingToggle settings={settings} settingPath={['other', 'onlyMedia']} onChange={onChange} label={<FormattedMessage id='community.column_settings.media_only' defaultMessage='Media Only' />} /> | ||||
|         </div> | ||||
|  | ||||
|         <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> | ||||
|  | ||||
|         <div className='column-settings__row'> | ||||
|   | ||||
| @@ -1,59 +0,0 @@ | ||||
| import PropTypes from 'prop-types'; | ||||
| import React, { Component, Fragment } from 'react'; | ||||
| import { FormattedMessage } from 'react-intl'; | ||||
| import { NavLink } from 'react-router-dom'; | ||||
|  | ||||
| export default class SectionHeadline extends Component { | ||||
|  | ||||
|   static propTypes = { | ||||
|     timelineId: PropTypes.string.isRequired, | ||||
|     to: PropTypes.string.isRequired, | ||||
|     pinned: PropTypes.bool.isRequired, | ||||
|     onlyMedia: PropTypes.bool.isRequired, | ||||
|     onClick: PropTypes.func, | ||||
|   }; | ||||
|  | ||||
|   shouldComponentUpdate (nextProps) { | ||||
|     return ( | ||||
|       this.props.onlyMedia !== nextProps.onlyMedia || | ||||
|       this.props.pinned !== nextProps.pinned || | ||||
|       this.props.to !== nextProps.to || | ||||
|       this.props.timelineId !== nextProps.timelineId | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   handleClick = e => { | ||||
|     const { onClick } = this.props; | ||||
|  | ||||
|     if (typeof onClick === 'function') { | ||||
|       e.preventDefault(); | ||||
|  | ||||
|       onClick.call(this, e); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   render () { | ||||
|     const { timelineId, to, pinned, onlyMedia } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <div className={`${timelineId}-timeline__section-headline`}> | ||||
|         {pinned ? ( | ||||
|           <Fragment> | ||||
|             <a href={to} className={!onlyMedia ? 'active' : undefined} onClick={this.handleClick}> | ||||
|               <FormattedMessage id='timeline.posts' defaultMessage='Toots' /> | ||||
|             </a> | ||||
|             <a href={`${to}/media`} className={onlyMedia ? 'active' : undefined} onClick={this.handleClick}> | ||||
|               <FormattedMessage id='timeline.media' defaultMessage='Media' /> | ||||
|             </a> | ||||
|           </Fragment> | ||||
|         ) : ( | ||||
|           <Fragment> | ||||
|             <NavLink exact to={to} replace><FormattedMessage id='timeline.posts' defaultMessage='Toots' /></NavLink> | ||||
|             <NavLink exact to={`${to}/media`} replace><FormattedMessage id='timeline.media' defaultMessage='Media' /></NavLink> | ||||
|           </Fragment> | ||||
|         )} | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,17 +1,28 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import ColumnSettings from '../components/column_settings'; | ||||
| import { changeSetting } from '../../../actions/settings'; | ||||
| import { changeColumnParams } from '../../../actions/columns'; | ||||
|  | ||||
| const mapStateToProps = state => ({ | ||||
|   settings: state.getIn(['settings', 'community']), | ||||
| }); | ||||
| const mapStateToProps = (state, { columnId }) => { | ||||
|   const uuid = columnId; | ||||
|   const columns = state.getIn(['settings', 'columns']); | ||||
|   const index = columns.findIndex(c => c.get('uuid') === uuid); | ||||
|  | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
|   return { | ||||
|     settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'community']), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
|   onChange (key, checked) { | ||||
|     dispatch(changeSetting(['community', ...key], checked)); | ||||
|   }, | ||||
|  | ||||
| }); | ||||
| const mapDispatchToProps = (dispatch, { columnId }) => { | ||||
|   return { | ||||
|     onChange (key, checked) { | ||||
|       if (columnId) { | ||||
|         dispatch(changeColumnParams(columnId, key, checked)); | ||||
|       } else { | ||||
|         dispatch(changeSetting(['community', ...key], checked)); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); | ||||
|   | ||||
| @@ -6,23 +6,33 @@ import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column from '../../components/column'; | ||||
| import ColumnHeader from '../../components/column_header'; | ||||
| import { expandCommunityTimeline } from '../../actions/timelines'; | ||||
| import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; | ||||
| import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | ||||
| import ColumnSettingsContainer from './containers/column_settings_container'; | ||||
| import SectionHeadline from './components/section_headline'; | ||||
| import { connectCommunityStream } from '../../actions/streaming'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'column.community', defaultMessage: 'Local timeline' }, | ||||
| }); | ||||
|  | ||||
| const mapStateToProps = (state, { onlyMedia }) => ({ | ||||
|   hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, | ||||
| }); | ||||
| const mapStateToProps = (state, { onlyMedia, columnId }) => { | ||||
|   const uuid = columnId; | ||||
|   const columns = state.getIn(['settings', 'columns']); | ||||
|   const index = columns.findIndex(c => c.get('uuid') === uuid); | ||||
|  | ||||
|   return { | ||||
|     hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, | ||||
|     onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @connect(mapStateToProps) | ||||
| @injectIntl | ||||
| export default class CommunityTimeline extends React.PureComponent { | ||||
|  | ||||
|   static contextTypes = { | ||||
|     router: PropTypes.object, | ||||
|   }; | ||||
|  | ||||
|   static defaultProps = { | ||||
|     onlyMedia: false, | ||||
|   }; | ||||
| @@ -89,27 +99,10 @@ export default class CommunityTimeline extends React.PureComponent { | ||||
|     dispatch(expandCommunityTimeline({ maxId, onlyMedia })); | ||||
|   } | ||||
|  | ||||
|   handleHeadlineLinkClick = e => { | ||||
|     const { columnId, dispatch } = this.props; | ||||
|     const onlyMedia = /\/media$/.test(e.currentTarget.href); | ||||
|  | ||||
|     dispatch(changeColumnParams(columnId, { other: { onlyMedia } })); | ||||
|   } | ||||
|  | ||||
|   render () { | ||||
|     const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props; | ||||
|     const pinned = !!columnId; | ||||
|  | ||||
|     const headline = ( | ||||
|       <SectionHeadline | ||||
|         timelineId='community' | ||||
|         to='/timelines/public/local' | ||||
|         pinned={pinned} | ||||
|         onlyMedia={onlyMedia} | ||||
|         onClick={this.handleHeadlineLinkClick} | ||||
|       /> | ||||
|     ); | ||||
|  | ||||
|     return ( | ||||
|       <Column ref={this.setRef}> | ||||
|         <ColumnHeader | ||||
| @@ -122,12 +115,10 @@ export default class CommunityTimeline extends React.PureComponent { | ||||
|           pinned={pinned} | ||||
|           multiColumn={multiColumn} | ||||
|         > | ||||
|           <ColumnSettingsContainer /> | ||||
|           <ColumnSettingsContainer columnId={columnId} /> | ||||
|         </ColumnHeader> | ||||
|  | ||||
|         <StatusListContainer | ||||
|           prepend={headline} | ||||
|           alwaysPrepend | ||||
|           trackScroll={!pinned} | ||||
|           scrollKey={`community_timeline-${columnId}`} | ||||
|           timelineId={`community${onlyMedia ? ':media' : ''}`} | ||||
|   | ||||
| @@ -0,0 +1,35 @@ | ||||
| import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
| import SettingText from '../../../components/setting_text'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   filter_regex: { id: 'home.column_settings.filter_regex', defaultMessage: 'Filter out by regular expressions' }, | ||||
|   settings: { id: 'home.settings', defaultMessage: 'Column settings' }, | ||||
| }); | ||||
|  | ||||
| @injectIntl | ||||
| export default class ColumnSettings extends React.PureComponent { | ||||
|  | ||||
|   static propTypes = { | ||||
|     settings: ImmutablePropTypes.map.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
|  | ||||
|   render () { | ||||
|     const { settings, onChange, intl } = this.props; | ||||
|  | ||||
|     return ( | ||||
|       <div> | ||||
|         <span className='column-settings__section'><FormattedMessage id='home.column_settings.advanced' defaultMessage='Advanced' /></span> | ||||
|  | ||||
|         <div className='column-settings__row'> | ||||
|           <SettingText settings={settings} settingKey={['regex', 'body']} onChange={onChange} label={intl.formatMessage(messages.filter_regex)} /> | ||||
|         </div> | ||||
|       </div> | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
| @@ -1,5 +1,5 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import ColumnSettings from '../../community_timeline/components/column_settings'; | ||||
| import ColumnSettings from '../components/column_settings'; | ||||
| import { changeSetting } from '../../../actions/settings'; | ||||
|  | ||||
| const mapStateToProps = state => ({ | ||||
|   | ||||
| @@ -1,17 +1,28 @@ | ||||
| import { connect } from 'react-redux'; | ||||
| import ColumnSettings from '../../community_timeline/components/column_settings'; | ||||
| import { changeSetting } from '../../../actions/settings'; | ||||
| import { changeColumnParams } from '../../../actions/columns'; | ||||
|  | ||||
| const mapStateToProps = state => ({ | ||||
|   settings: state.getIn(['settings', 'public']), | ||||
| }); | ||||
| const mapStateToProps = (state, { columnId }) => { | ||||
|   const uuid = columnId; | ||||
|   const columns = state.getIn(['settings', 'columns']); | ||||
|   const index = columns.findIndex(c => c.get('uuid') === uuid); | ||||
|  | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
|   return { | ||||
|     settings: (uuid && index >= 0) ? columns.get(index).get('params') : state.getIn(['settings', 'public']), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
|   onChange (key, checked) { | ||||
|     dispatch(changeSetting(['public', ...key], checked)); | ||||
|   }, | ||||
|  | ||||
| }); | ||||
| const mapDispatchToProps = (dispatch, { columnId }) => { | ||||
|   return { | ||||
|     onChange (key, checked) { | ||||
|       if (columnId) { | ||||
|         dispatch(changeColumnParams(columnId, key, checked)); | ||||
|       } else { | ||||
|         dispatch(changeSetting(['public', ...key], checked)); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| export default connect(mapStateToProps, mapDispatchToProps)(ColumnSettings); | ||||
|   | ||||
| @@ -6,23 +6,33 @@ import StatusListContainer from '../ui/containers/status_list_container'; | ||||
| import Column from '../../components/column'; | ||||
| import ColumnHeader from '../../components/column_header'; | ||||
| import { expandPublicTimeline } from '../../actions/timelines'; | ||||
| import { addColumn, removeColumn, moveColumn, changeColumnParams } from '../../actions/columns'; | ||||
| import { addColumn, removeColumn, moveColumn } from '../../actions/columns'; | ||||
| import ColumnSettingsContainer from './containers/column_settings_container'; | ||||
| import SectionHeadline from '../community_timeline/components/section_headline'; | ||||
| import { connectPublicStream } from '../../actions/streaming'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   title: { id: 'column.public', defaultMessage: 'Federated timeline' }, | ||||
| }); | ||||
|  | ||||
| const mapStateToProps = (state, { onlyMedia }) => ({ | ||||
|   hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0, | ||||
| }); | ||||
| const mapStateToProps = (state, { onlyMedia, columnId }) => { | ||||
|   const uuid = columnId; | ||||
|   const columns = state.getIn(['settings', 'columns']); | ||||
|   const index = columns.findIndex(c => c.get('uuid') === uuid); | ||||
|  | ||||
|   return { | ||||
|     hasUnread: state.getIn(['timelines', `public${onlyMedia ? ':media' : ''}`, 'unread']) > 0, | ||||
|     onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'public', 'other', 'onlyMedia']), | ||||
|   }; | ||||
| }; | ||||
|  | ||||
| @connect(mapStateToProps) | ||||
| @injectIntl | ||||
| export default class PublicTimeline extends React.PureComponent { | ||||
|  | ||||
|   static contextTypes = { | ||||
|     router: PropTypes.object, | ||||
|   }; | ||||
|  | ||||
|   static defaultProps = { | ||||
|     onlyMedia: false, | ||||
|   }; | ||||
| @@ -89,27 +99,17 @@ export default class PublicTimeline extends React.PureComponent { | ||||
|     dispatch(expandPublicTimeline({ maxId, onlyMedia })); | ||||
|   } | ||||
|  | ||||
|   handleHeadlineLinkClick = e => { | ||||
|     const { columnId, dispatch } = this.props; | ||||
|     const onlyMedia = /\/media$/.test(e.currentTarget.href); | ||||
|  | ||||
|     dispatch(changeColumnParams(columnId, { other: { onlyMedia } })); | ||||
|   handleSettingChanged = (key, checked) => { | ||||
|     const { columnId } = this.props; | ||||
|     if (!columnId && key[0] === 'other' && key[1] === 'onlyMedia') { | ||||
|       this.context.router.history.replace(`/timelines/public${checked ? '/media' : ''}`); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   render () { | ||||
|     const { intl, columnId, hasUnread, multiColumn, onlyMedia } = this.props; | ||||
|     const pinned = !!columnId; | ||||
|  | ||||
|     const headline = ( | ||||
|       <SectionHeadline | ||||
|         timelineId='public' | ||||
|         to='/timelines/public' | ||||
|         pinned={pinned} | ||||
|         onlyMedia={onlyMedia} | ||||
|         onClick={this.handleHeadlineLinkClick} | ||||
|       /> | ||||
|     ); | ||||
|  | ||||
|     return ( | ||||
|       <Column ref={this.setRef}> | ||||
|         <ColumnHeader | ||||
| @@ -122,12 +122,10 @@ export default class PublicTimeline extends React.PureComponent { | ||||
|           pinned={pinned} | ||||
|           multiColumn={multiColumn} | ||||
|         > | ||||
|           <ColumnSettingsContainer /> | ||||
|           <ColumnSettingsContainer onChange={this.handleSettingChanged} columnId={columnId} /> | ||||
|         </ColumnHeader> | ||||
|  | ||||
|         <StatusListContainer | ||||
|           prepend={headline} | ||||
|           alwaysPrepend | ||||
|           timelineId={`public${onlyMedia ? ':media' : ''}`} | ||||
|           onLoadMore={this.handleLoadMore} | ||||
|           trackScroll={!pinned} | ||||
|   | ||||
| @@ -93,11 +93,11 @@ const moveColumn = (state, uuid, direction) => { | ||||
|     .set('saved', false); | ||||
| }; | ||||
|  | ||||
| const changeColumnParams = (state, uuid, params) => { | ||||
| const changeColumnParams = (state, uuid, path, value) => { | ||||
|   const columns = state.get('columns'); | ||||
|   const index   = columns.findIndex(item => item.get('uuid') === uuid); | ||||
|  | ||||
|   const newColumns = columns.update(index, column => column.update('params', () => fromJS(params))); | ||||
|   const newColumns = columns.update(index, column => column.updateIn(['params', ...path], () => value)); | ||||
|  | ||||
|   return state | ||||
|     .set('columns', newColumns) | ||||
| @@ -127,7 +127,7 @@ export default function settings(state = initialState, action) { | ||||
|   case COLUMN_MOVE: | ||||
|     return moveColumn(state, action.uuid, action.direction); | ||||
|   case COLUMN_PARAMS_CHANGE: | ||||
|     return changeColumnParams(state, action.uuid, action.params); | ||||
|     return changeColumnParams(state, action.uuid, action.path, action.value); | ||||
|   case EMOJI_USE: | ||||
|     return updateFrequentEmojis(state, action.emoji); | ||||
|   case SETTING_SAVE: | ||||
|   | ||||
| @@ -4806,8 +4806,6 @@ a.status-card { | ||||
|   } | ||||
| } | ||||
|  | ||||
| .community-timeline__section-headline, | ||||
| .public-timeline__section-headline, | ||||
| .account__section-headline { | ||||
|   background: darken($ui-base-color, 4%); | ||||
|   border-bottom: 1px solid lighten($ui-base-color, 8%); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user