diff options
9 files changed, 67 insertions, 9 deletions
diff --git a/Gemfile.lock b/Gemfile.lock index 87298fc77..cb480ab6d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -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) diff --git a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb index f1fa03f0a..8518c61ee 100644 --- a/app/controllers/settings/two_factor_authentication/confirmations_controller.rb +++ b/app/controllers/settings/two_factor_authentication/confirmations_controller.rb @@ -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 diff --git a/app/javascript/mastodon/features/getting_started/index.js b/app/javascript/mastodon/features/getting_started/index.js index ee789e180..3a875169e 100644 --- a/app/javascript/mastodon/features/getting_started/index.js +++ b/app/javascript/mastodon/features/getting_started/index.js @@ -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) { diff --git a/app/javascript/mastodon/features/ui/components/column_link.js b/app/javascript/mastodon/features/ui/components/column_link.js index a90616213..25c2d1cf8 100644 --- a/app/javascript/mastodon/features/ui/components/column_link.js +++ b/app/javascript/mastodon/features/ui/components/column_link.js @@ -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; diff --git a/app/javascript/mastodon/reducers/statuses.js b/app/javascript/mastodon/reducers/statuses.js index 5120b2b67..cc0d4dacd 100644 --- a/app/javascript/mastodon/reducers/statuses.js +++ b/app/javascript/mastodon/reducers/statuses.js @@ -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; diff --git a/app/javascript/styles/mastodon/components.scss b/app/javascript/styles/mastodon/components.scss index 75e37237b..3eb941a23 100644 --- a/app/javascript/styles/mastodon/components.scss +++ b/app/javascript/styles/mastodon/components.scss @@ -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; diff --git a/app/views/notification_mailer/_status.html.haml b/app/views/notification_mailer/_status.html.haml index 85f9294e9..727e914dc 100644 --- a/app/views/notification_mailer/_status.html.haml +++ b/app/views/notification_mailer/_status.html.haml @@ -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}" diff --git a/config/webpack/configuration.js b/config/webpack/configuration.js index f6b4d4c61..c58d8028e 100644 --- a/config/webpack/configuration.js +++ b/config/webpack/configuration.js @@ -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 = { diff --git a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb index 0676d6161..aee82a3d8 100644 --- a/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb +++ b/spec/controllers/settings/two_factor_authentication/confirmations_controller_spec.rb @@ -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 |