diff --git a/Dockerfile b/Dockerfile index 081981d467..69153c0300 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,6 +56,7 @@ RUN apt-get update && \ useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \ apt-get -y --no-install-recommends install whois \ wget \ + procps \ libssl1.1 \ libpq5 \ imagemagick \ diff --git a/app/controllers/api/v1/tags_controller.rb b/app/controllers/api/v1/tags_controller.rb index 0966ee4699..272362c314 100644 --- a/app/controllers/api/v1/tags_controller.rb +++ b/app/controllers/api/v1/tags_controller.rb @@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController end def follow - TagFollow.first_or_create!(tag: @tag, account: current_account, rate_limit: true) + TagFollow.create_with(rate_limit: true).find_or_create_by!(tag: @tag, account: current_account) render json: @tag, serializer: REST::TagSerializer end diff --git a/app/javascript/flavours/glitch/features/compose/components/compose_form.js b/app/javascript/flavours/glitch/features/compose/components/compose_form.js index 516648f4b3..abdd247a0b 100644 --- a/app/javascript/flavours/glitch/features/compose/components/compose_form.js +++ b/app/javascript/flavours/glitch/features/compose/components/compose_form.js @@ -356,10 +356,8 @@ class ComposeForm extends ImmutablePureComponent { 0)} spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler} diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js index 6b6d3de948..3de198c452 100644 --- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js @@ -9,13 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button'; import DropdownMenu from './dropdown_menu'; // Utils. -import { isUserTouching } from 'flavours/glitch/is_mobile'; import { assignHandlers } from 'flavours/glitch/utils/react_helpers'; // The component. export default class ComposerOptionsDropdown extends React.PureComponent { static propTypes = { + isUserTouching: PropTypes.func, disabled: PropTypes.bool, icon: PropTypes.string, items: PropTypes.arrayOf(PropTypes.shape({ @@ -49,7 +49,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent { const { onModalOpen } = this.props; const { open } = this.state; - if (isUserTouching()) { + if (this.props.isUserTouching && this.props.isUserTouching()) { if (this.state.open) { this.props.onModalClose(); } else { diff --git a/app/javascript/flavours/glitch/features/compose/components/options.js b/app/javascript/flavours/glitch/features/compose/components/options.js index c6278f4cb5..b5276c3715 100644 --- a/app/javascript/flavours/glitch/features/compose/components/options.js +++ b/app/javascript/flavours/glitch/features/compose/components/options.js @@ -10,8 +10,8 @@ import { connect } from 'react-redux'; // Components. import IconButton from 'flavours/glitch/components/icon_button'; import TextIconButton from './text_icon_button'; -import Dropdown from './dropdown'; -import PrivacyDropdown from './privacy_dropdown'; +import DropdownContainer from '../containers/dropdown_container'; +import PrivacyDropdownContainer from '../containers/privacy_dropdown_container'; import LanguageDropdown from '../containers/language_dropdown_container'; import ImmutablePureComponent from 'react-immutable-pure-component'; @@ -126,15 +126,11 @@ class ComposerOptions extends ImmutablePureComponent { hasPoll: PropTypes.bool, intl: PropTypes.object.isRequired, onChangeAdvancedOption: PropTypes.func, - onChangeVisibility: PropTypes.func, onChangeContentType: PropTypes.func, onTogglePoll: PropTypes.func, onDoodleOpen: PropTypes.func, - onModalClose: PropTypes.func, - onModalOpen: PropTypes.func, onToggleSpoiler: PropTypes.func, onUpload: PropTypes.func, - privacy: PropTypes.string, contentType: PropTypes.string, resetFileKey: PropTypes.number, spoiler: PropTypes.bool, @@ -195,12 +191,8 @@ class ComposerOptions extends ImmutablePureComponent { hasPoll, onChangeAdvancedOption, onChangeContentType, - onChangeVisibility, onTogglePoll, - onModalClose, - onModalOpen, onToggleSpoiler, - privacy, resetFileKey, spoiler, showContentTypeChoice, @@ -239,7 +231,7 @@ class ComposerOptions extends ImmutablePureComponent { multiple style={{ display: 'none' }} /> - {!!pollLimits && ( @@ -275,15 +265,9 @@ class ComposerOptions extends ImmutablePureComponent { /> )}
- + {showContentTypeChoice && ( - @@ -308,7 +290,7 @@ class ComposerOptions extends ImmutablePureComponent { /> )} - diff --git a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js index 14364b0a08..02cf722891 100644 --- a/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js +++ b/app/javascript/flavours/glitch/features/compose/components/privacy_dropdown.js @@ -32,7 +32,7 @@ class PrivacyDropdown extends React.PureComponent { }; render () { - const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, intl: { formatMessage } } = this.props; + const { value, onChange, onModalOpen, onModalClose, disabled, noDirect, container, isUserTouching, intl: { formatMessage } } = this.props; // We predefine our privacy items so that we can easily pick the // dropdown icon later. @@ -75,6 +75,7 @@ class PrivacyDropdown extends React.PureComponent { icon={(privacyItems[value] || {}).icon} items={items} onChange={onChange} + isUserTouching={isUserTouching} onModalClose={onModalClose} onModalOpen={onModalOpen} title={formatMessage(messages.change_privacy)} diff --git a/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js new file mode 100644 index 0000000000..3ac16505fc --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/dropdown_container.js @@ -0,0 +1,12 @@ +import { connect } from 'react-redux'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; +import { openModal, closeModal } from 'flavours/glitch/actions/modal'; +import Dropdown from '../components/dropdown'; + +const mapDispatchToProps = dispatch => ({ + isUserTouching, + onModalOpen: props => dispatch(openModal('ACTIONS', props)), + onModalClose: () => dispatch(closeModal()), +}); + +export default connect(null, mapDispatchToProps)(Dropdown); diff --git a/app/javascript/flavours/glitch/features/compose/containers/options_container.js b/app/javascript/flavours/glitch/features/compose/containers/options_container.js index c792aa5827..6c3db8173d 100644 --- a/app/javascript/flavours/glitch/features/compose/containers/options_container.js +++ b/app/javascript/flavours/glitch/features/compose/containers/options_container.js @@ -6,7 +6,7 @@ import { addPoll, removePoll, } from 'flavours/glitch/actions/compose'; -import { closeModal, openModal } from 'flavours/glitch/actions/modal'; +import { openModal } from 'flavours/glitch/actions/modal'; function mapStateToProps (state) { const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']); @@ -48,14 +48,6 @@ const mapDispatchToProps = (dispatch) => ({ onDoodleOpen() { dispatch(openModal('DOODLE', { noEsc: true })); }, - - onModalClose() { - dispatch(closeModal()); - }, - - onModalOpen(props) { - dispatch(openModal('ACTIONS', props)); - }, }); export default connect(mapStateToProps, mapDispatchToProps)(Options); diff --git a/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js new file mode 100644 index 0000000000..5591d89c46 --- /dev/null +++ b/app/javascript/flavours/glitch/features/compose/containers/privacy_dropdown_container.js @@ -0,0 +1,23 @@ +import { connect } from 'react-redux'; +import PrivacyDropdown from '../components/privacy_dropdown'; +import { changeComposeVisibility } from 'flavours/glitch/actions/compose'; +import { openModal, closeModal } from 'flavours/glitch/actions/modal'; +import { isUserTouching } from 'flavours/glitch/is_mobile'; + +const mapStateToProps = state => ({ + value: state.getIn(['compose', 'privacy']), +}); + +const mapDispatchToProps = dispatch => ({ + + onChange (value) { + dispatch(changeComposeVisibility(value)); + }, + + isUserTouching, + onModalOpen: props => dispatch(openModal('ACTIONS', props)), + onModalClose: () => dispatch(closeModal()), + +}); + +export default connect(mapStateToProps, mapDispatchToProps)(PrivacyDropdown); diff --git a/app/models/account/field.rb b/app/models/account/field.rb index ffc8dce80b..4db4cac301 100644 --- a/app/models/account/field.rb +++ b/app/models/account/field.rb @@ -46,7 +46,8 @@ class Account::Field < ActiveModelSerializers::Model parsed_url.user.nil? && parsed_url.password.nil? && parsed_url.host.present? && - parsed_url.normalized_host == parsed_url.host + parsed_url.normalized_host == parsed_url.host && + (parsed_url.path.empty? || parsed_url.path == parsed_url.normalized_path) rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError false end diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index 94a9ad55e8..af65fa9a1a 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -25,7 +25,7 @@ module Mastodon end def suffix_version - '+1.0.2' + '+1.0.3' end def to_a diff --git a/spec/controllers/api/v1/tags_controller_spec.rb b/spec/controllers/api/v1/tags_controller_spec.rb index ac42660dfa..216faad872 100644 --- a/spec/controllers/api/v1/tags_controller_spec.rb +++ b/spec/controllers/api/v1/tags_controller_spec.rb @@ -33,7 +33,11 @@ RSpec.describe Api::V1::TagsController, type: :controller do end describe 'POST #follow' do + let!(:unrelated_tag) { Fabricate(:tag) } + before do + TagFollow.create!(account: user.account, tag: unrelated_tag) + post :follow, params: { id: name } end diff --git a/spec/models/account/field_spec.rb b/spec/models/account/field_spec.rb index b4beec0483..0ac9769bcc 100644 --- a/spec/models/account/field_spec.rb +++ b/spec/models/account/field_spec.rb @@ -67,7 +67,15 @@ RSpec.describe Account::Field, type: :model do end context 'for an IDN URL' do - let(:value) { 'http://twitter.com∕dougallj∕status∕1590357240443437057.ê.cc/twitter.html' } + let(:value) { 'https://twitter.com∕dougallj∕status∕1590357240443437057.ê.cc/twitter.html' } + + it 'returns false' do + expect(subject.verifiable?).to be false + end + end + + context 'for a URL with a non-normalized path' do + let(:value) { 'https://github.com/octocatxxxxxxxx/../mastodon' } it 'returns false' do expect(subject.verifiable?).to be false