commit
3a58d82dc0
17
CHANGELOG.md
17
CHANGELOG.md
@ -3,6 +3,23 @@ Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## [3.4.4] - 2021-11-26
|
||||
### Fixed
|
||||
|
||||
- Fix error when suspending user with an already blocked canonical email ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17036))
|
||||
- Fix overflow of long profile fields in admin UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17010))
|
||||
- Fix confusing error when WebFinger request returns empty document ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16986))
|
||||
- Fix upload of remote media with OpenStack Swift sometimes failing ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16998))
|
||||
- Fix logout link not working in Safari ([noellabo](https://github.com/mastodon/mastodon/pull/16574))
|
||||
- Fix “open” link of media modal not closing modal in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16524))
|
||||
- Fix replying from modal in web UI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16516))
|
||||
- Fix `mastodon:setup` command crashing in some circumstances ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/16976))
|
||||
|
||||
### Security
|
||||
|
||||
- Fix filtering DMs from non-followed users ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17042))
|
||||
- Fix handling of recursive toots in WebUI ([ClearlyClaire](https://github.com/mastodon/mastodon/pull/17041))
|
||||
|
||||
## [3.4.3] - 2021-11-06
|
||||
### Fixed
|
||||
|
||||
|
@ -6,6 +6,10 @@ import { multiply } from 'color-blend';
|
||||
|
||||
export default class ModalRoot extends React.PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
router: PropTypes.object,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
|
@ -21,6 +21,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
closeWhenConfirm: false,
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
},
|
||||
|
@ -86,6 +86,7 @@ class Compose extends React.PureComponent {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
closeWhenConfirm: false,
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
|
||||
|
@ -118,7 +118,11 @@ class Footer extends ImmutablePureComponent {
|
||||
return;
|
||||
}
|
||||
|
||||
const { status } = this.props;
|
||||
const { status, onClose } = this.props;
|
||||
|
||||
if (onClose) {
|
||||
onClose();
|
||||
}
|
||||
|
||||
router.history.push(`/statuses/${status.get('id')}`);
|
||||
}
|
||||
|
@ -88,7 +88,7 @@ const makeMapStateToProps = () => {
|
||||
ancestorsIds = ancestorsIds.withMutations(mutable => {
|
||||
let id = statusId;
|
||||
|
||||
while (id) {
|
||||
while (id && !mutable.includes(id)) {
|
||||
mutable.unshift(id);
|
||||
id = inReplyTos.get(id);
|
||||
}
|
||||
@ -106,7 +106,7 @@ const makeMapStateToProps = () => {
|
||||
const ids = [statusId];
|
||||
|
||||
while (ids.length > 0) {
|
||||
let id = ids.shift();
|
||||
let id = ids.pop();
|
||||
const replies = contextReplies.get(id);
|
||||
|
||||
if (statusId !== id) {
|
||||
@ -115,7 +115,7 @@ const makeMapStateToProps = () => {
|
||||
|
||||
if (replies) {
|
||||
replies.reverse().forEach(reply => {
|
||||
ids.unshift(reply);
|
||||
if (!ids.includes(reply) && !descendantsIds.includes(reply) && statusId !== reply) ids.push(reply);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -13,15 +13,22 @@ class ConfirmationModal extends React.PureComponent {
|
||||
onConfirm: PropTypes.func.isRequired,
|
||||
secondary: PropTypes.string,
|
||||
onSecondary: PropTypes.func,
|
||||
closeWhenConfirm: PropTypes.bool,
|
||||
intl: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
closeWhenConfirm: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.button.focus();
|
||||
}
|
||||
|
||||
handleClick = () => {
|
||||
if (this.props.closeWhenConfirm) {
|
||||
this.props.onClose();
|
||||
}
|
||||
this.props.onConfirm();
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: intl.formatMessage(messages.logoutMessage),
|
||||
confirm: intl.formatMessage(messages.logoutConfirm),
|
||||
closeWhenConfirm: false,
|
||||
onConfirm: () => logOut(),
|
||||
}));
|
||||
},
|
||||
|
@ -829,6 +829,7 @@ a.name-tag,
|
||||
padding: 0 5px;
|
||||
margin-bottom: 10px;
|
||||
flex: 1 0 50%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.account__header__fields,
|
||||
|
@ -46,7 +46,9 @@ class Webfinger
|
||||
def body_from_webfinger(url = standard_url, use_fallback = true)
|
||||
webfinger_request(url).perform do |res|
|
||||
if res.code == 200
|
||||
res.body_with_limit
|
||||
body = res.body_with_limit
|
||||
raise Webfinger::Error, "Request for #{@uri} returned empty response" if body.empty?
|
||||
body
|
||||
elsif res.code == 404 && use_fallback
|
||||
body_from_host_meta
|
||||
elsif res.code == 410
|
||||
|
@ -15,7 +15,7 @@ class CanonicalEmailBlock < ApplicationRecord
|
||||
|
||||
belongs_to :reference_account, class_name: 'Account'
|
||||
|
||||
validates :canonical_email_hash, presence: true
|
||||
validates :canonical_email_hash, presence: true, uniqueness: true
|
||||
|
||||
def email=(email)
|
||||
self.canonical_email_hash = email_to_canonical_email_hash(email)
|
||||
|
@ -67,8 +67,49 @@ class NotifyService < BaseService
|
||||
message? && @notification.target_status.direct_visibility?
|
||||
end
|
||||
|
||||
# Returns true if the sender has been mentionned by the recipient up the thread
|
||||
def response_to_recipient?
|
||||
@notification.target_status.in_reply_to_account_id == @recipient.id && @notification.target_status.thread&.direct_visibility?
|
||||
return false if @notification.target_status.in_reply_to_id.nil?
|
||||
|
||||
# Using an SQL CTE to avoid unneeded back-and-forth with SQL server in case of long threads
|
||||
!Status.count_by_sql([<<-SQL.squish, id: @notification.target_status.in_reply_to_id, recipient_id: @recipient.id, sender_id: @notification.from_account.id]).zero?
|
||||
WITH RECURSIVE ancestors(id, in_reply_to_id, replying_to_sender) AS (
|
||||
SELECT
|
||||
s.id, s.in_reply_to_id, (CASE
|
||||
WHEN s.account_id = :recipient_id THEN
|
||||
EXISTS (
|
||||
SELECT *
|
||||
FROM mentions m
|
||||
WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
|
||||
)
|
||||
ELSE
|
||||
FALSE
|
||||
END)
|
||||
FROM statuses s
|
||||
WHERE s.id = :id
|
||||
UNION ALL
|
||||
SELECT
|
||||
s.id,
|
||||
s.in_reply_to_id,
|
||||
(CASE
|
||||
WHEN s.account_id = :recipient_id THEN
|
||||
EXISTS (
|
||||
SELECT *
|
||||
FROM mentions m
|
||||
WHERE m.silent = FALSE AND m.account_id = :sender_id AND m.status_id = s.id
|
||||
)
|
||||
ELSE
|
||||
FALSE
|
||||
END)
|
||||
FROM ancestors st
|
||||
JOIN statuses s ON s.id = st.in_reply_to_id
|
||||
WHERE st.replying_to_sender IS FALSE
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
FROM ancestors st
|
||||
JOIN statuses s ON s.id = st.id
|
||||
WHERE st.replying_to_sender IS TRUE AND s.visibility = 3
|
||||
SQL
|
||||
end
|
||||
|
||||
def from_staff?
|
||||
|
@ -13,7 +13,7 @@ module Mastodon
|
||||
end
|
||||
|
||||
def patch
|
||||
3
|
||||
4
|
||||
end
|
||||
|
||||
def flags
|
||||
|
@ -17,9 +17,9 @@ module Paperclip
|
||||
|
||||
def cache_current_values
|
||||
@original_filename = filename_from_content_disposition.presence || filename_from_path.presence || 'data'
|
||||
@size = @target.response.content_length
|
||||
@tempfile = copy_to_tempfile(@target)
|
||||
@content_type = ContentTypeDetector.new(@tempfile.path).detect
|
||||
@size = File.size(@tempfile)
|
||||
end
|
||||
|
||||
def copy_to_tempfile(source)
|
||||
|
@ -350,11 +350,11 @@ namespace :mastodon do
|
||||
end
|
||||
end.join("\n")
|
||||
|
||||
generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n"
|
||||
generated_header = "# Generated with mastodon:setup on #{Time.now.utc}\n\n".dup
|
||||
|
||||
if incompatible_syntax
|
||||
generated_header << "Some variables in this file will be interpreted differently whether you are\n"
|
||||
generated_header << "using docker-compose or not.\n\n"
|
||||
generated_header << "# Some variables in this file will be interpreted differently whether you are\n"
|
||||
generated_header << "# using docker-compose or not.\n\n"
|
||||
end
|
||||
|
||||
File.write(Rails.root.join('.env.production'), "#{generated_header}#{env_contents}\n")
|
||||
|
@ -5,6 +5,37 @@ RSpec.describe Account, type: :model do
|
||||
let(:bob) { Fabricate(:account, username: 'bob') }
|
||||
subject { Fabricate(:account) }
|
||||
|
||||
describe '#suspend!' do
|
||||
it 'marks the account as suspended' do
|
||||
subject.suspend!
|
||||
expect(subject.suspended?).to be true
|
||||
end
|
||||
|
||||
it 'creates a deletion request' do
|
||||
subject.suspend!
|
||||
expect(AccountDeletionRequest.where(account: subject).exists?).to be true
|
||||
end
|
||||
|
||||
context 'when the account is of a local user' do
|
||||
let!(:subject) { Fabricate(:account, user: Fabricate(:user, email: 'foo+bar@domain.org')) }
|
||||
|
||||
it 'creates a canonical domain block' do
|
||||
subject.suspend!
|
||||
expect(CanonicalEmailBlock.block?(subject.user_email)).to be true
|
||||
end
|
||||
|
||||
context 'when a canonical domain block already exists for that email' do
|
||||
before do
|
||||
Fabricate(:canonical_email_block, email: subject.user_email)
|
||||
end
|
||||
|
||||
it 'does not raise an error' do
|
||||
expect { subject.suspend! }.not_to raise_error
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#follow!' do
|
||||
it 'creates a follow' do
|
||||
follow = subject.follow!(bob)
|
||||
|
@ -64,8 +64,9 @@ RSpec.describe NotifyService, type: :service do
|
||||
is_expected.to_not change(Notification, :count)
|
||||
end
|
||||
|
||||
context 'if the message chain initiated by recipient, but is not direct message' do
|
||||
context 'if the message chain is initiated by recipient, but is not direct message' do
|
||||
let(:reply_to) { Fabricate(:status, account: recipient) }
|
||||
let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
|
||||
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
|
||||
|
||||
it 'does not notify' do
|
||||
@ -73,8 +74,20 @@ RSpec.describe NotifyService, type: :service do
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the message chain initiated by recipient and is direct message' do
|
||||
context 'if the message chain is initiated by recipient, but without a mention to the sender, even if the sender sends multiple messages in a row' do
|
||||
let(:reply_to) { Fabricate(:status, account: recipient) }
|
||||
let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
|
||||
let(:dummy_reply) { Fabricate(:status, account: sender, visibility: :direct, thread: reply_to) }
|
||||
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: dummy_reply)) }
|
||||
|
||||
it 'does not notify' do
|
||||
is_expected.to_not change(Notification, :count)
|
||||
end
|
||||
end
|
||||
|
||||
context 'if the message chain is initiated by the recipient with a mention to the sender' do
|
||||
let(:reply_to) { Fabricate(:status, account: recipient, visibility: :direct) }
|
||||
let!(:mention) { Fabricate(:mention, account: sender, status: reply_to) }
|
||||
let(:activity) { Fabricate(:mention, account: recipient, status: Fabricate(:status, account: sender, visibility: :direct, thread: reply_to)) }
|
||||
|
||||
it 'does notify' do
|
||||
|
Loading…
Reference in New Issue
Block a user