mirror of
https://git.bsd.gay/fef/nyastodon.git
synced 2025-01-08 12:47:03 +01:00
merge catstodon/main into main
This commit is contained in:
commit
8380f9fc2c
13 changed files with 64 additions and 44 deletions
|
@ -56,6 +56,7 @@ RUN apt-get update && \
|
||||||
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
|
useradd -u "$UID" -g "${GID}" -m -d /opt/mastodon mastodon && \
|
||||||
apt-get -y --no-install-recommends install whois \
|
apt-get -y --no-install-recommends install whois \
|
||||||
wget \
|
wget \
|
||||||
|
procps \
|
||||||
libssl1.1 \
|
libssl1.1 \
|
||||||
libpq5 \
|
libpq5 \
|
||||||
imagemagick \
|
imagemagick \
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Api::V1::TagsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
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
|
render json: @tag, serializer: REST::TagSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -356,10 +356,8 @@ class ComposeForm extends ImmutablePureComponent {
|
||||||
<OptionsContainer
|
<OptionsContainer
|
||||||
advancedOptions={advancedOptions}
|
advancedOptions={advancedOptions}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
onChangeVisibility={onChangeVisibility}
|
|
||||||
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
|
onToggleSpoiler={spoilersAlwaysOn ? null : onChangeSpoilerness}
|
||||||
onUpload={onPaste}
|
onUpload={onPaste}
|
||||||
privacy={privacy}
|
|
||||||
isEditing={isEditing}
|
isEditing={isEditing}
|
||||||
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
|
sensitive={sensitive || (spoilersAlwaysOn && spoilerText && spoilerText.length > 0)}
|
||||||
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
|
spoiler={spoilersAlwaysOn ? (spoilerText && spoilerText.length > 0) : spoiler}
|
||||||
|
|
|
@ -9,13 +9,13 @@ import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import DropdownMenu from './dropdown_menu';
|
import DropdownMenu from './dropdown_menu';
|
||||||
|
|
||||||
// Utils.
|
// Utils.
|
||||||
import { isUserTouching } from 'flavours/glitch/is_mobile';
|
|
||||||
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
|
import { assignHandlers } from 'flavours/glitch/utils/react_helpers';
|
||||||
|
|
||||||
// The component.
|
// The component.
|
||||||
export default class ComposerOptionsDropdown extends React.PureComponent {
|
export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
isUserTouching: PropTypes.func,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
icon: PropTypes.string,
|
icon: PropTypes.string,
|
||||||
items: PropTypes.arrayOf(PropTypes.shape({
|
items: PropTypes.arrayOf(PropTypes.shape({
|
||||||
|
@ -49,7 +49,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
|
||||||
const { onModalOpen } = this.props;
|
const { onModalOpen } = this.props;
|
||||||
const { open } = this.state;
|
const { open } = this.state;
|
||||||
|
|
||||||
if (isUserTouching()) {
|
if (this.props.isUserTouching && this.props.isUserTouching()) {
|
||||||
if (this.state.open) {
|
if (this.state.open) {
|
||||||
this.props.onModalClose();
|
this.props.onModalClose();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,8 +10,8 @@ import { connect } from 'react-redux';
|
||||||
// Components.
|
// Components.
|
||||||
import IconButton from 'flavours/glitch/components/icon_button';
|
import IconButton from 'flavours/glitch/components/icon_button';
|
||||||
import TextIconButton from './text_icon_button';
|
import TextIconButton from './text_icon_button';
|
||||||
import Dropdown from './dropdown';
|
import DropdownContainer from '../containers/dropdown_container';
|
||||||
import PrivacyDropdown from './privacy_dropdown';
|
import PrivacyDropdownContainer from '../containers/privacy_dropdown_container';
|
||||||
import LanguageDropdown from '../containers/language_dropdown_container';
|
import LanguageDropdown from '../containers/language_dropdown_container';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
|
|
||||||
|
@ -126,15 +126,11 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
hasPoll: PropTypes.bool,
|
hasPoll: PropTypes.bool,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onChangeAdvancedOption: PropTypes.func,
|
onChangeAdvancedOption: PropTypes.func,
|
||||||
onChangeVisibility: PropTypes.func,
|
|
||||||
onChangeContentType: PropTypes.func,
|
onChangeContentType: PropTypes.func,
|
||||||
onTogglePoll: PropTypes.func,
|
onTogglePoll: PropTypes.func,
|
||||||
onDoodleOpen: PropTypes.func,
|
onDoodleOpen: PropTypes.func,
|
||||||
onModalClose: PropTypes.func,
|
|
||||||
onModalOpen: PropTypes.func,
|
|
||||||
onToggleSpoiler: PropTypes.func,
|
onToggleSpoiler: PropTypes.func,
|
||||||
onUpload: PropTypes.func,
|
onUpload: PropTypes.func,
|
||||||
privacy: PropTypes.string,
|
|
||||||
contentType: PropTypes.string,
|
contentType: PropTypes.string,
|
||||||
resetFileKey: PropTypes.number,
|
resetFileKey: PropTypes.number,
|
||||||
spoiler: PropTypes.bool,
|
spoiler: PropTypes.bool,
|
||||||
|
@ -195,12 +191,8 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
hasPoll,
|
hasPoll,
|
||||||
onChangeAdvancedOption,
|
onChangeAdvancedOption,
|
||||||
onChangeContentType,
|
onChangeContentType,
|
||||||
onChangeVisibility,
|
|
||||||
onTogglePoll,
|
onTogglePoll,
|
||||||
onModalClose,
|
|
||||||
onModalOpen,
|
|
||||||
onToggleSpoiler,
|
onToggleSpoiler,
|
||||||
privacy,
|
|
||||||
resetFileKey,
|
resetFileKey,
|
||||||
spoiler,
|
spoiler,
|
||||||
showContentTypeChoice,
|
showContentTypeChoice,
|
||||||
|
@ -239,7 +231,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
multiple
|
multiple
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
/>
|
/>
|
||||||
<Dropdown
|
<DropdownContainer
|
||||||
disabled={disabled || !allowMedia}
|
disabled={disabled || !allowMedia}
|
||||||
icon='paperclip'
|
icon='paperclip'
|
||||||
items={[
|
items={[
|
||||||
|
@ -255,8 +247,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onChange={this.handleClickAttach}
|
onChange={this.handleClickAttach}
|
||||||
onModalClose={onModalClose}
|
|
||||||
onModalOpen={onModalOpen}
|
|
||||||
title={formatMessage(messages.attach)}
|
title={formatMessage(messages.attach)}
|
||||||
/>
|
/>
|
||||||
{!!pollLimits && (
|
{!!pollLimits && (
|
||||||
|
@ -275,15 +265,9 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
<PrivacyDropdown
|
<PrivacyDropdownContainer disabled={disabled || isEditing} />
|
||||||
disabled={disabled || isEditing}
|
|
||||||
onChange={onChangeVisibility}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
onModalOpen={onModalOpen}
|
|
||||||
value={privacy}
|
|
||||||
/>
|
|
||||||
{showContentTypeChoice && (
|
{showContentTypeChoice && (
|
||||||
<Dropdown
|
<DropdownContainer
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
|
icon={(contentTypeItems[contentType.split('/')[1]] || {}).icon}
|
||||||
items={[
|
items={[
|
||||||
|
@ -292,8 +276,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
contentTypeItems.markdown,
|
contentTypeItems.markdown,
|
||||||
]}
|
]}
|
||||||
onChange={onChangeContentType}
|
onChange={onChangeContentType}
|
||||||
onModalClose={onModalClose}
|
|
||||||
onModalOpen={onModalOpen}
|
|
||||||
title={formatMessage(messages.content_type)}
|
title={formatMessage(messages.content_type)}
|
||||||
value={contentType}
|
value={contentType}
|
||||||
/>
|
/>
|
||||||
|
@ -308,7 +290,7 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<LanguageDropdown />
|
<LanguageDropdown />
|
||||||
<Dropdown
|
<DropdownContainer
|
||||||
disabled={disabled || isEditing}
|
disabled={disabled || isEditing}
|
||||||
icon='ellipsis-h'
|
icon='ellipsis-h'
|
||||||
items={advancedOptions ? [
|
items={advancedOptions ? [
|
||||||
|
@ -325,8 +307,6 @@ class ComposerOptions extends ImmutablePureComponent {
|
||||||
] : null}
|
] : null}
|
||||||
onChange={onChangeAdvancedOption}
|
onChange={onChangeAdvancedOption}
|
||||||
renderItemContents={this.renderToggleItemContents}
|
renderItemContents={this.renderToggleItemContents}
|
||||||
onModalClose={onModalClose}
|
|
||||||
onModalOpen={onModalOpen}
|
|
||||||
title={formatMessage(messages.advanced_options_icon_title)}
|
title={formatMessage(messages.advanced_options_icon_title)}
|
||||||
closeOnChange={false}
|
closeOnChange={false}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -32,7 +32,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
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
|
// We predefine our privacy items so that we can easily pick the
|
||||||
// dropdown icon later.
|
// dropdown icon later.
|
||||||
|
@ -75,6 +75,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
icon={(privacyItems[value] || {}).icon}
|
icon={(privacyItems[value] || {}).icon}
|
||||||
items={items}
|
items={items}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
isUserTouching={isUserTouching}
|
||||||
onModalClose={onModalClose}
|
onModalClose={onModalClose}
|
||||||
onModalOpen={onModalOpen}
|
onModalOpen={onModalOpen}
|
||||||
title={formatMessage(messages.change_privacy)}
|
title={formatMessage(messages.change_privacy)}
|
||||||
|
|
|
@ -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);
|
|
@ -6,7 +6,7 @@ import {
|
||||||
addPoll,
|
addPoll,
|
||||||
removePoll,
|
removePoll,
|
||||||
} from 'flavours/glitch/actions/compose';
|
} from 'flavours/glitch/actions/compose';
|
||||||
import { closeModal, openModal } from 'flavours/glitch/actions/modal';
|
import { openModal } from 'flavours/glitch/actions/modal';
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
|
const spoilersAlwaysOn = state.getIn(['local_settings', 'always_show_spoilers_field']);
|
||||||
|
@ -48,14 +48,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
onDoodleOpen() {
|
onDoodleOpen() {
|
||||||
dispatch(openModal('DOODLE', { noEsc: true }));
|
dispatch(openModal('DOODLE', { noEsc: true }));
|
||||||
},
|
},
|
||||||
|
|
||||||
onModalClose() {
|
|
||||||
dispatch(closeModal());
|
|
||||||
},
|
|
||||||
|
|
||||||
onModalOpen(props) {
|
|
||||||
dispatch(openModal('ACTIONS', props));
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(Options);
|
export default connect(mapStateToProps, mapDispatchToProps)(Options);
|
||||||
|
|
|
@ -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);
|
|
@ -46,7 +46,8 @@ class Account::Field < ActiveModelSerializers::Model
|
||||||
parsed_url.user.nil? &&
|
parsed_url.user.nil? &&
|
||||||
parsed_url.password.nil? &&
|
parsed_url.password.nil? &&
|
||||||
parsed_url.host.present? &&
|
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
|
rescue Addressable::URI::InvalidURIError, IDN::Idna::IdnaError
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,7 +25,7 @@ module Mastodon
|
||||||
end
|
end
|
||||||
|
|
||||||
def suffix_version
|
def suffix_version
|
||||||
'+1.0.2'
|
'+1.0.3'
|
||||||
end
|
end
|
||||||
|
|
||||||
def post_suffix
|
def post_suffix
|
||||||
|
|
|
@ -33,7 +33,11 @@ RSpec.describe Api::V1::TagsController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #follow' do
|
describe 'POST #follow' do
|
||||||
|
let!(:unrelated_tag) { Fabricate(:tag) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
|
TagFollow.create!(account: user.account, tag: unrelated_tag)
|
||||||
|
|
||||||
post :follow, params: { id: name }
|
post :follow, params: { id: name }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,15 @@ RSpec.describe Account::Field, type: :model do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'for an IDN URL' do
|
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
|
it 'returns false' do
|
||||||
expect(subject.verifiable?).to be false
|
expect(subject.verifiable?).to be false
|
||||||
|
|
Loading…
Reference in a new issue