Merge remote-tracking branch 'origin/master' into merge-upstream
Conflicts: app/controllers/settings/two_factor_authentication/confirmations_controller.rb
This commit is contained in:
		| @@ -70,7 +70,7 @@ GEM | ||||
|       coderay (>= 1.0.0) | ||||
|       erubi (>= 1.0.0) | ||||
|       rack (>= 0.9.0) | ||||
|     binding_of_caller (0.7.3) | ||||
|     binding_of_caller (0.8.0) | ||||
|       debug_inspector (>= 0.0.1) | ||||
|     bootsnap (1.1.5) | ||||
|       msgpack (~> 1.0) | ||||
|   | ||||
| @@ -3,6 +3,8 @@ | ||||
| module Settings | ||||
|   module TwoFactorAuthentication | ||||
|     class ConfirmationsController < BaseController | ||||
|       before_action :ensure_otp_secret | ||||
|  | ||||
|       def new | ||||
|         prepare_two_factor_form | ||||
|       end | ||||
| @@ -34,6 +36,10 @@ module Settings | ||||
|         @provision_url = current_user.otp_provisioning_uri(current_user.email, issuer: Rails.configuration.x.local_domain) | ||||
|         @qrcode = RQRCode::QRCode.new(@provision_url) | ||||
|       end | ||||
|  | ||||
|       def ensure_otp_secret | ||||
|         redirect_to settings_two_factor_authentication_path unless current_user.otp_secret | ||||
|       end | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -8,6 +8,8 @@ import PropTypes from 'prop-types'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import { me } from '../../initial_state'; | ||||
| import { fetchFollowRequests } from '../../actions/accounts'; | ||||
| import { List as ImmutableList } from 'immutable'; | ||||
|  | ||||
| const messages = defineMessages({ | ||||
|   heading: { id: 'getting_started.heading', defaultMessage: 'Getting started' }, | ||||
| @@ -32,9 +34,25 @@ const messages = defineMessages({ | ||||
| const mapStateToProps = state => ({ | ||||
|   myAccount: state.getIn(['accounts', me]), | ||||
|   columns: state.getIn(['settings', 'columns']), | ||||
|   unreadFollowRequests: state.getIn(['user_lists', 'follow_requests', 'items'], ImmutableList()).size, | ||||
|   unreadNotifications: state.getIn(['notifications', 'unread']), | ||||
| }); | ||||
|  | ||||
| @connect(mapStateToProps) | ||||
| const mapDispatchToProps = dispatch => ({ | ||||
|   fetchFollowRequests: () => dispatch(fetchFollowRequests()), | ||||
| }); | ||||
|  | ||||
| const badgeDisplay = (number, limit) => { | ||||
|   if (number === 0) { | ||||
|     return undefined; | ||||
|   } else if (limit && number >= limit) { | ||||
|     return `${limit}+`; | ||||
|   } else { | ||||
|     return number; | ||||
|   } | ||||
| }; | ||||
|  | ||||
| @connect(mapStateToProps, mapDispatchToProps) | ||||
| @injectIntl | ||||
| export default class GettingStarted extends ImmutablePureComponent { | ||||
|  | ||||
| @@ -43,10 +61,21 @@ export default class GettingStarted extends ImmutablePureComponent { | ||||
|     myAccount: ImmutablePropTypes.map.isRequired, | ||||
|     columns: ImmutablePropTypes.list, | ||||
|     multiColumn: PropTypes.bool, | ||||
|     fetchFollowRequests: PropTypes.func.isRequired, | ||||
|     unreadFollowRequests: PropTypes.number, | ||||
|     unreadNotifications: PropTypes.number, | ||||
|   }; | ||||
|  | ||||
|   componentDidMount () { | ||||
|     const { myAccount, fetchFollowRequests } = this.props; | ||||
|  | ||||
|     if (myAccount.get('locked')) { | ||||
|       fetchFollowRequests(); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   render () { | ||||
|     const { intl, myAccount, columns, multiColumn } = this.props; | ||||
|     const { intl, myAccount, columns, multiColumn, unreadFollowRequests, unreadNotifications } = this.props; | ||||
|  | ||||
|     const navItems = []; | ||||
|  | ||||
| @@ -56,7 +85,7 @@ export default class GettingStarted extends ImmutablePureComponent { | ||||
|       } | ||||
|  | ||||
|       if (!columns.find(item => item.get('id') === 'NOTIFICATIONS')) { | ||||
|         navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} to='/notifications' />); | ||||
|         navItems.push(<ColumnLink key='1' icon='bell' text={intl.formatMessage(messages.notifications)} badge={badgeDisplay(unreadNotifications)} to='/notifications' />); | ||||
|       } | ||||
|  | ||||
|       if (!columns.find(item => item.get('id') === 'COMMUNITY')) { | ||||
| @@ -74,7 +103,7 @@ export default class GettingStarted extends ImmutablePureComponent { | ||||
|     ); | ||||
|  | ||||
|     if (myAccount.get('locked')) { | ||||
|       navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} to='/follow_requests' />); | ||||
|       navItems.push(<ColumnLink key='6' icon='users' text={intl.formatMessage(messages.follow_requests)} badge={badgeDisplay(unreadFollowRequests, 40)} to='/follow_requests' />); | ||||
|     } | ||||
|  | ||||
|     if (multiColumn) { | ||||
|   | ||||
| @@ -2,12 +2,15 @@ import React from 'react'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { Link } from 'react-router-dom'; | ||||
|  | ||||
| const ColumnLink = ({ icon, text, to, href, method }) => { | ||||
| const ColumnLink = ({ icon, text, to, href, method, badge }) => { | ||||
|   const badgeElement = typeof badge !== 'undefined' ? <span className='column-link__badge'>{badge}</span> : null; | ||||
|  | ||||
|   if (href) { | ||||
|     return ( | ||||
|       <a href={href} className='column-link' data-method={method}> | ||||
|         <i className={`fa fa-fw fa-${icon} column-link__icon`} /> | ||||
|         {text} | ||||
|         {badgeElement} | ||||
|       </a> | ||||
|     ); | ||||
|   } else { | ||||
| @@ -15,6 +18,7 @@ const ColumnLink = ({ icon, text, to, href, method }) => { | ||||
|       <Link to={to} className='column-link'> | ||||
|         <i className={`fa fa-fw fa-${icon} column-link__icon`} /> | ||||
|         {text} | ||||
|         {badgeElement} | ||||
|       </Link> | ||||
|     ); | ||||
|   } | ||||
| @@ -26,6 +30,7 @@ ColumnLink.propTypes = { | ||||
|   to: PropTypes.string, | ||||
|   href: PropTypes.string, | ||||
|   method: PropTypes.string, | ||||
|   badge: PropTypes.node, | ||||
| }; | ||||
|  | ||||
| export default ColumnLink; | ||||
|   | ||||
| @@ -54,7 +54,7 @@ const normalizeStatus = (state, status) => { | ||||
|     normalStatus.reblog = status.reblog.id; | ||||
|   } | ||||
|  | ||||
|   const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br \/>/g, '\n').replace(/<\/p><p>/g, '\n\n'); | ||||
|   const searchContent = [status.spoiler_text, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n'); | ||||
|  | ||||
|   const emojiMap = normalStatus.emojis.reduce((obj, emoji) => { | ||||
|     obj[`:${emoji.shortcode}:`] = emoji; | ||||
|   | ||||
| @@ -2070,6 +2070,17 @@ | ||||
|   margin-right: 5px; | ||||
| } | ||||
|  | ||||
| .column-link__badge { | ||||
|   display: inline-block; | ||||
|   border-radius: 4px; | ||||
|   font-size: 12px; | ||||
|   line-height: 19px; | ||||
|   font-weight: 500; | ||||
|   background: $ui-base-color; | ||||
|   padding: 4px 8px; | ||||
|   margin: -6px 10px; | ||||
| } | ||||
|  | ||||
| .column-subheading { | ||||
|   background: $ui-base-color; | ||||
|   color: $ui-base-lighter-color; | ||||
|   | ||||
| @@ -19,7 +19,7 @@ | ||||
|                                 %tbody | ||||
|                                   %tr | ||||
|                                     %td{ align: 'left', width: 48 } | ||||
|                                       = image_tag full_asset_url(status.account.avatar), alt:'' | ||||
|                                       = image_tag full_asset_url(status.account.avatar.url), alt:'' | ||||
|                                     %td{ align: 'left' } | ||||
|                                       %bdi= display_name(status.account) | ||||
|                                       = "@#{status.account.acct}" | ||||
|   | ||||
| @@ -73,7 +73,7 @@ function formatPublicPath(host = '', path = '') { | ||||
|  | ||||
| const output = { | ||||
|   path: resolve('public', settings.public_output_path), | ||||
|   publicPath: formatPublicPath(env.ASSET_HOST || env.LOCAL_DOMAIN, settings.public_output_path), | ||||
|   publicPath: formatPublicPath(env.ASSET_HOST || env.WEB_DOMAIN || env.LOCAL_DOMAIN, settings.public_output_path), | ||||
| }; | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -6,6 +6,7 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do | ||||
|   render_views | ||||
|  | ||||
|   let(:user) { Fabricate(:user, email: 'local-part@domain', otp_secret: 'thisisasecretforthespecofnewview') } | ||||
|   let(:user_without_otp_secret) { Fabricate(:user, email: 'local-part@domain') } | ||||
|  | ||||
|   shared_examples 'renders :new' do | ||||
|     it 'renders the new view' do | ||||
| @@ -33,6 +34,12 @@ describe Settings::TwoFactorAuthentication::ConfirmationsController do | ||||
|       get :new | ||||
|       expect(response).to redirect_to('/auth/sign_in') | ||||
|     end | ||||
|  | ||||
|     it 'redirects if user do not have otp_secret' do | ||||
|       sign_in user_without_otp_secret, scope: :user | ||||
|       get :new | ||||
|       expect(response).to redirect_to('/settings/two_factor_authentication') | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'POST #create' do | ||||
|   | ||||
		Reference in New Issue
	
	Block a user