Merge pull request #1305 from ThibG/glitch-soc/merge-upstream

Merge upstream changes
main
ThibG 5 years ago committed by GitHub
commit d101438b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep
SHELL ["bash", "-c"]
# Install Node v12 (LTS)
ENV NODE_VER="12.14.0"
ENV NODE_VER="12.16.1"
RUN ARCH= && \
dpkgArch="$(dpkg --print-architecture)" && \
case "${dpkgArch##*-}" in \

@ -1,7 +1,7 @@
# frozen_string_literal: true
source 'https://rubygems.org'
ruby '>= 2.4.0', '< 3.0.0'
ruby '>= 2.5.0', '< 3.0.0'
gem 'pkg-config', '~> 1.4'
@ -35,7 +35,7 @@ gem 'browser'
gem 'charlock_holmes', '~> 0.7.7'
gem 'iso-639'
gem 'chewy', '~> 5.1'
gem 'cld3', '~> 3.2.6'
gem 'cld3', '~> 3.3.0'
gem 'devise', '~> 4.7'
gem 'devise-two-factor', '~> 3.1'
@ -84,7 +84,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.1'
gem 'sidekiq', '~> 5.2'
gem 'sidekiq', '~> 6.0'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0'
gem 'sidekiq-bulk', '~>0.2.0'
@ -145,7 +145,7 @@ group :development do
gem 'brakeman', '~> 4.7', require: false
gem 'bundler-audit', '~> 0.6', require: false
gem 'capistrano', '~> 3.11'
gem 'capistrano', '~> 3.12'
gem 'capistrano-rails', '~> 1.4'
gem 'capistrano-rbenv', '~> 2.1'
gem 'capistrano-yarn', '~> 2.0'

@ -128,7 +128,7 @@ GEM
bundler (>= 1.2.0, < 3)
thor (~> 0.18)
byebug (11.1.1)
capistrano (3.11.2)
capistrano (3.12.1)
airbrussh (>= 1.0.0)
i18n
rake (>= 10.0.0)
@ -160,7 +160,7 @@ GEM
elasticsearch (>= 2.0.0)
elasticsearch-dsl
chunky_png (1.3.11)
cld3 (3.2.6)
cld3 (3.3.0)
ffi (>= 1.1.0, < 1.12.0)
climate_control (0.2.0)
cocaine (0.5.8)
@ -214,7 +214,7 @@ GEM
encryptor (3.0.0)
equatable (0.6.1)
erubi (1.9.0)
et-orbi (1.1.6)
et-orbi (1.2.3)
tzinfo
excon (0.71.0)
fabrication (2.21.0)
@ -241,8 +241,8 @@ GEM
fog-json (>= 1.0)
ipaddress (>= 0.8)
formatador (0.2.5)
fugit (1.1.6)
et-orbi (~> 1.1, >= 1.1.6)
fugit (1.3.3)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1)
fuubar (2.5.0)
rspec-core (~> 3.0)
@ -265,8 +265,8 @@ GEM
railties (>= 4.0.1)
hamster (3.0.0)
concurrent-ruby (~> 1.0)
hashdiff (1.0.0)
hashie (3.6.0)
hashdiff (1.0.1)
hashie (4.1.0)
highline (2.0.3)
hiredis (0.6.3)
hkdf (0.3.0)
@ -304,9 +304,9 @@ GEM
jmespath (1.4.0)
json (2.3.0)
json-canonicalization (0.2.0)
json-ld (3.1.0)
json-ld (3.1.1)
htmlentities (~> 4.3)
json-canonicalization (~> 0.1)
json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8)
multi_json (~> 1.14)
rack (~> 2.0)
@ -384,8 +384,8 @@ GEM
sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.3)
omniauth (1.9.0)
hashie (>= 3.4.6, < 3.7.0)
omniauth (1.9.1)
hashie (>= 3.4.6)
rack (>= 1.6.2, < 3)
omniauth-cas (1.1.1)
addressable (~> 2.3)
@ -445,7 +445,7 @@ GEM
rack (>= 1.0, < 3)
rack-cors (1.1.1)
rack (>= 2.0.0)
rack-protection (2.0.7)
rack-protection (2.0.8.1)
rack
rack-proxy (0.6.5)
rack
@ -556,32 +556,34 @@ GEM
ruby-progressbar (1.10.1)
ruby-saml (1.9.0)
nokogiri (>= 1.5.10)
rufus-scheduler (3.5.2)
fugit (~> 1.1, >= 1.1.5)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.5)
sanitize (5.1.0)
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
sidekiq (5.2.7)
connection_pool (~> 2.2, >= 2.2.2)
rack (>= 1.5.0)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
sidekiq (6.0.4)
connection_pool (>= 2.2.2)
rack (>= 2.0.0)
rack-protection (>= 2.0.0)
redis (>= 4.1.0)
sidekiq-bulk (0.2.0)
sidekiq
sidekiq-scheduler (3.0.0)
sidekiq-scheduler (3.0.1)
e2mmap
redis (>= 3, < 5)
rufus-scheduler (~> 3.2)
sidekiq (>= 3)
thwait
tilt (>= 1.4.0)
sidekiq-unique-jobs (6.0.18)
sidekiq-unique-jobs (6.0.20)
concurrent-ruby (~> 1.0, >= 1.0.5)
sidekiq (>= 4.0, < 7.0)
thor (~> 0)
simple-navigation (4.1.0)
activesupport (>= 2.3.2)
simple_form (5.0.1)
simple_form (5.0.2)
actionpack (>= 5.0)
activemodel (>= 5.0)
simplecov (0.18.2)
@ -595,7 +597,7 @@ GEM
actionpack (>= 4.0)
activesupport (>= 4.0)
sprockets (>= 3.0.0)
sshkit (1.20.0)
sshkit (1.21.0)
net-scp (>= 1.1.2)
net-ssh (>= 2.8.0)
stackprof (0.2.15)
@ -640,7 +642,7 @@ GEM
uniform_notifier (1.13.0)
warden (1.2.8)
rack (>= 2.0.6)
webmock (3.8.0)
webmock (3.8.3)
addressable (>= 2.3.6)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
@ -675,14 +677,14 @@ DEPENDENCIES
browser
bullet (~> 6.1)
bundler-audit (~> 0.6)
capistrano (~> 3.11)
capistrano (~> 3.12)
capistrano-rails (~> 1.4)
capistrano-rbenv (~> 2.1)
capistrano-yarn (~> 2.0)
capybara (~> 3.31)
charlock_holmes (~> 0.7.7)
chewy (~> 5.1)
cld3 (~> 3.2.6)
cld3 (~> 3.3.0)
climate_control (~> 0.2)
concurrent-ruby
connection_pool
@ -767,7 +769,7 @@ DEPENDENCIES
rubocop-rails (~> 2.4)
ruby-progressbar (~> 1.10)
sanitize (~> 5.1)
sidekiq (~> 5.2)
sidekiq (~> 6.0)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
sidekiq-unique-jobs (~> 6.0)

@ -47,6 +47,11 @@ class StatusesIndex < Chewy::Index
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
crutch :bookmarks do |collection|
data = ::Bookmark.where(status_id: collection.map(&:id)).where(account: Account.local).pluck(:status_id, :account_id)
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end
root date_detection: false do
field :id, type: 'long'
field :account_id, type: 'long'

@ -6,12 +6,12 @@ module Admin
def index
authorize :email_domain_block, :index?
@email_domain_blocks = EmailDomainBlock.page(params[:page])
@email_domain_blocks = EmailDomainBlock.where(parent_id: nil).includes(:children).order(id: :desc).page(params[:page])
end
def new
authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new
@email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
end
def create
@ -21,6 +21,28 @@ module Admin
if @email_domain_block.save
log_action :create, @email_domain_block
if @email_domain_block.with_dns_records?
hostnames = []
ips = []
Resolv::DNS.open do |dns|
dns.timeouts = 1
hostnames = dns.getresources(@email_domain_block.domain, Resolv::DNS::Resource::IN::MX).to_a.map { |e| e.exchange.to_s }
([@email_domain_block.domain] + hostnames).uniq.each do |hostname|
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::A).to_a.map { |e| e.address.to_s })
ips.concat(dns.getresources(hostname, Resolv::DNS::Resource::IN::AAAA).to_a.map { |e| e.address.to_s })
end
end
(hostnames + ips).each do |hostname|
another_email_domain_block = EmailDomainBlock.new(domain: hostname, parent: @email_domain_block)
log_action :create, another_email_domain_block if another_email_domain_block.save
end
end
redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else
render :new
@ -41,7 +63,7 @@ module Admin
end
def resource_params
params.require(:email_domain_block).permit(:domain)
params.require(:email_domain_block).permit(:domain, :with_dns_records)
end
end
end

@ -7,7 +7,7 @@ module Admin
def index
authorize :account_warning_preset, :index?
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
@warning_preset = AccountWarningPreset.new
end
@ -19,7 +19,7 @@ module Admin
if @warning_preset.save
redirect_to admin_warning_presets_path
else
@warning_presets = AccountWarningPreset.all
@warning_presets = AccountWarningPreset.alphabetic
render :index
end
end
@ -52,7 +52,7 @@ module Admin
end
def warning_preset_params
params.require(:account_warning_preset).permit(:text)
params.require(:account_warning_preset).permit(:title, :text)
end
end
end

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end
def hide_results?
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_followers? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end
def hide_results?
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
(@account.hides_following? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
end
def default_accounts

@ -3,25 +3,42 @@
class Api::V1::MediaController < Api::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:media' }
before_action :require_user!
before_action :set_media_attachment, except: [:create]
before_action :check_processing, except: [:create]
def create
@media = current_account.media_attachments.create!(media_params)
render json: @media, serializer: REST::MediaAttachmentSerializer
@media_attachment = current_account.media_attachments.create!(media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
render json: processing_error, status: 500
end
def show
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
def update
@media = current_account.media_attachments.where(status_id: nil).find(params[:id])
@media.update!(media_params)
render json: @media, serializer: REST::MediaAttachmentSerializer
@media_attachment.update!(media_attachment_params)
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: status_code_for_media_attachment
end
private
def media_params
def status_code_for_media_attachment
@media_attachment.not_processed? ? 206 : 200
end
def set_media_attachment
@media_attachment = current_account.media_attachments.unattached.find(params[:id])
end
def check_processing
render json: processing_error, status: 422 if @media_attachment.processing_failed?
end
def media_attachment_params
params.permit(:file, :description, :focus)
end

@ -0,0 +1,12 @@
# frozen_string_literal: true
class Api::V2::MediaController < Api::V1::MediaController
def create
@media_attachment = current_account.media_attachments.create!({ delay_processing: true }.merge(media_attachment_params))
render json: @media_attachment, serializer: REST::MediaAttachmentSerializer, status: 202
rescue Paperclip::Errors::NotIdentifiedByImageMagickError
render json: file_type_error, status: 422
rescue Paperclip::Error
render json: processing_error, status: 500
end
end

@ -29,7 +29,8 @@ class FollowerAccountsController < ApplicationController
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
content_type: 'application/activity+json',
fields: restrict_fields_to
end
end
end
@ -72,4 +73,12 @@ class FollowerAccountsController < ApplicationController
)
end
end
def restrict_fields_to
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
end
end
end

@ -29,7 +29,8 @@ class FollowingAccountsController < ApplicationController
render json: collection_presenter,
serializer: ActivityPub::CollectionSerializer,
adapter: ActivityPub::Adapter,
content_type: 'application/activity+json'
content_type: 'application/activity+json',
fields: restrict_fields_to
end
end
end
@ -72,4 +73,12 @@ class FollowingAccountsController < ApplicationController
)
end
end
def restrict_fields_to
if page_requested? || !@account.user_hides_network?
# Return all fields
else
%i(id type totalItems)
end
end
end

@ -1,6 +1,6 @@
// This file will be loaded on admin pages, regardless of theme.
import { delegate } from 'rails-ujs';
import { delegate } from '@rails/ujs';
import ready from '../mastodon/ready';
const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';

@ -3,7 +3,7 @@
import createHistory from 'history/createBrowserHistory';
import ready from '../mastodon/ready';
const { delegate } = require('rails-ujs');
const { delegate } = require('@rails/ujs');
const { length } = require('stringz');
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {

@ -1,7 +1,7 @@
// This file will be loaded on settings pages, regardless of theme.
import escapeTextContentForBrowser from 'escape-html';
const { delegate } = require('rails-ujs');
const { delegate } = require('@rails/ujs');
import emojify from '../mastodon/features/emoji/emoji';
delegate(document, '#account_display_name', 'input', ({ target }) => {

@ -259,12 +259,31 @@ export function uploadCompose(files) {
// Account for disparity in size of original image and resized data
total += file.size - f.size;
return api(getState).post('/api/v1/media', data, {
return api(getState).post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){
progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
},
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).then(({ status, data }) => {
// If server-side processing of the media attachment has not completed yet,
// poll the server until it is, before showing the media attachment as uploaded
if (status === 200) {
dispatch(uploadComposeSuccess(data, f));
} else if (status === 202) {
const poll = () => {
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
if (response.status === 200) {
dispatch(uploadComposeSuccess(response.data, f));
} else if (response.status === 206) {
setTimeout(() => poll(), 1000);
}
}).catch(error => dispatch(uploadComposeFail(error)));
};
poll();
}
});
}).catch(error => dispatch(uploadComposeFail(error)));
};
};

@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
export default @injectIntl

@ -6,7 +6,7 @@ import Domain from '../components/domain';
import { openModal } from '../actions/modal';
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {

@ -40,7 +40,7 @@ const messages = defineMessages({
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
@ -136,7 +136,7 @@ class Header extends ImmutablePureComponent {
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
info.push(<span className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
}
if (me !== account.get('id')) {

@ -13,8 +13,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from 'flavours/glitch/components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
const mapStateToProps = state => ({
@ -54,7 +54,7 @@ class Blocks extends ImmutablePureComponent {
);
}
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>

@ -389,7 +389,7 @@ class Announcements extends ImmutablePureComponent {
_markAnnouncementAsRead () {
const { dismissAnnouncement, announcements } = this.props;
const { index } = this.state;
const announcement = announcements.get(index);
const announcement = announcements.get(index) || announcements.get(index - 1);
if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
}
@ -407,7 +407,7 @@ class Announcements extends ImmutablePureComponent {
render () {
const { announcements, intl } = this.props;
const { index } = this.state;
const index = this.state.index < announcements.size ? this.state.index : announcements.size - 1;
if (announcements.isEmpty()) {
return null;

@ -1,4 +1,4 @@
import { start } from 'rails-ujs';
import { start } from '@rails/ujs';
start();

@ -5,7 +5,7 @@ import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extension
function main() {
const IntlMessageFormat = require('intl-messageformat').default;
const { timeAgoString } = require('flavours/glitch/components/relative_timestamp');
const { delegate } = require('rails-ujs');
const { delegate } = require('@rails/ujs');
const emojify = require('flavours/glitch/util/emoji').default;
const { getLocale } = require('locales');
const { messages } = getLocale();

@ -3,7 +3,7 @@ import ready from 'flavours/glitch/util/ready';
import loadKeyboardExtensions from 'flavours/glitch/util/load_keyboard_extensions';
function main() {
const { delegate } = require('rails-ujs');
const { delegate } = require('@rails/ujs');
delegate(document, '.sidebar__toggle__icon', 'click', () => {
const target = document.querySelector('.sidebar ul');

@ -1,4 +1,4 @@
import Rails from 'rails-ujs';
import Rails from '@rails/ujs';
import { signOutLink } from 'flavours/glitch/util/backend_links';
export const logOut = () => {

@ -230,12 +230,31 @@ export function uploadCompose(files) {
// Account for disparity in size of original image and resized data
total += file.size - f.size;
return api(getState).post('/api/v1/media', data, {
return api(getState).post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){
progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total));
},
}).then(({ data }) => dispatch(uploadComposeSuccess(data, f)));
}).then(({ status, data }) => {
// If server-side processing of the media attachment has not completed yet,
// poll the server until it is, before showing the media attachment as uploaded
if (status === 200) {
dispatch(uploadComposeSuccess(data, f));
} else if (status === 202) {
const poll = () => {
api(getState).get(`/api/v1/media/${data.id}`).then(response => {
if (response.status === 200) {
dispatch(uploadComposeSuccess(response.data, f));
} else if (response.status === 206) {
setTimeout(() => poll(), 1000);
}
}).catch(error => dispatch(uploadComposeFail(error)));
};
poll();
}
});
}).catch(error => dispatch(uploadComposeFail(error)));
};
};

@ -1,4 +1,4 @@
import Rails from 'rails-ujs';
import Rails from '@rails/ujs';
export function start() {
require('font-awesome/css/font-awesome.css');

@ -5,7 +5,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
const messages = defineMessages({
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
export default @injectIntl

@ -82,15 +82,19 @@ export default class ScrollableList extends PureComponent {
lastScrollWasSynthetic = false;
scrollToTopOnMouseIdle = false;
_getScrollingElement = () => {
if (this.props.bindToDocument) {
return (document.scrollingElement || document.body);
} else {
return this.node;
}
}
setScrollTop = newScrollTop => {
if (this.getScrollTop() !== newScrollTop) {
this.lastScrollWasSynthetic = true;
if (this.props.bindToDocument) {
document.scrollingElement.scrollTop = newScrollTop;
} else {
this.node.scrollTop = newScrollTop;
}
this._getScrollingElement().scrollTop = newScrollTop;
}
};
@ -151,15 +155,15 @@ export default class ScrollableList extends PureComponent {
}
getScrollTop = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollTop : this.node.scrollTop;
return this._getScrollingElement().scrollTop;
}
getScrollHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.scrollHeight : this.node.scrollHeight;
return this._getScrollingElement().scrollHeight;
}
getClientHeight = () => {
return this.props.bindToDocument ? document.scrollingElement.clientHeight : this.node.clientHeight;
return this._getScrollingElement().clientHeight;
}
updateScrollBottom = (snapshot) => {

@ -6,7 +6,7 @@ import Domain from '../components/domain';
import { openModal } from '../actions/modal';
const messages = defineMessages({
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Block entire domain' },
});
const makeMapStateToProps = () => {

@ -39,7 +39,7 @@ const messages = defineMessages({
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Hidden domains' },
domain_blocks: { id: 'navigation_bar.domain_blocks', defaultMessage: 'Blocked domains' },
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t feature on profile' },
@ -142,7 +142,7 @@ class Header extends ImmutablePureComponent {
if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) {
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain hidden' /></span>);
info.push(<span key='domain_blocked' className='relationship-tag'><FormattedMessage id='account.domain_blocked' defaultMessage='Domain blocked' /></span>);
}
if (me !== account.get('id')) {

@ -16,6 +16,7 @@ const messages = defineMessages({
mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
filters: { id: 'navigation_bar.filters', defaultMessage: 'Muted words' },
logout: { id: 'navigation_bar.logout', defaultMessage: 'Logout' },
bookmarks: { id: 'navigation_bar.bookmarks', defaultMessage: 'Bookmarks' },
});
export default @injectIntl
@ -42,6 +43,7 @@ class ActionBar extends React.PureComponent {
menu.push(null);
menu.push({ text: intl.formatMessage(messages.follow_requests), to: '/follow_requests' });
menu.push({ text: intl.formatMessage(messages.favourites), to: '/favourites' });
menu.push({ text: intl.formatMessage(messages.bookmarks), to: '/bookmarks' });
menu.push({ text: intl.formatMessage(messages.lists), to: '/lists' });
menu.push(null);
menu.push({ text: intl.formatMessage(messages.mutes), to: '/mutes' });

@ -13,8 +13,8 @@ import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_bloc
import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
});
const mapStateToProps = state => ({
@ -55,7 +55,7 @@ class Blocks extends ImmutablePureComponent {
);
}
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no hidden domains yet.' />;
const emptyMessage = <FormattedMessage id='empty_column.domain_blocks' defaultMessage='There are no blocked domains yet.' />;
return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>

@ -389,7 +389,7 @@ class Announcements extends ImmutablePureComponent {
_markAnnouncementAsRead () {
const { dismissAnnouncement, announcements } = this.props;
const { index } = this.state;
const announcement = announcements.get(index);
const announcement = announcements.get(index) || announcements.get(index - 1);
if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
}
@ -407,7 +407,7 @@ class Announcements extends ImmutablePureComponent {
render () {
const { announcements, intl } = this.props;
const { index } = this.state;
const index = this.state.index < announcements.size ? this.state.index : announcements.size - 1;
if (announcements.isEmpty()) {
return null;

@ -166,7 +166,7 @@ export default class DetailedStatus extends ImmutablePureComponent {
reblogIcon = 'lock';
}
if (status.get('visibility') === 'private') {
if (['private', 'direct'].includes(status.get('visibility'))) {
reblogLink = <Icon id={reblogIcon} />;
} else if (this.context.router) {
reblogLink = (

@ -523,7 +523,7 @@
{
"descriptors": [
{
"defaultMessage": "Hide entire domain",
"defaultMessage": "Block entire domain",
"id": "confirmations.domain_block.confirm"
},
{
@ -737,7 +737,7 @@
"id": "navigation_bar.blocks"
},
{
"defaultMessage": "Hidden domains",
"defaultMessage": "Blocked domains",
"id": "navigation_bar.domain_blocks"
},
{
@ -773,7 +773,7 @@
"id": "account.muted"
},
{
"defaultMessage": "Domain hidden",
"defaultMessage": "Domain blocked",
"id": "account.domain_blocked"
},
{
@ -917,6 +917,10 @@
{
"defaultMessage": "Logout",
"id": "navigation_bar.logout"
},
{
"defaultMessage": "Bookmarks",
"id": "navigation_bar.bookmarks"
}
],
"path": "app/javascript/mastodon/features/compose/components/action_bar.json"
@ -1466,7 +1470,7 @@
{
"descriptors": [
{
"defaultMessage": "Hidden domains",
"defaultMessage": "Blocked domains",
"id": "column.domain_blocks"
},
{
@ -1474,7 +1478,7 @@
"id": "account.unblock_domain"
},
{
"defaultMessage": "There are no hidden domains yet.",
"defaultMessage": "There are no blocked domains yet.",
"id": "empty_column.domain_blocks"
}
],
@ -2957,4 +2961,4 @@
],
"path": "app/javascript/mastodon/features/video/index.json"
}
]
]

@ -7,7 +7,7 @@
"account.blocked": "Blocked",
"account.cancel_follow_request": "Cancel follow request",
"account.direct": "Direct message @{name}",
"account.domain_blocked": "Domain hidden",
"account.domain_blocked": "Domain blocked",
"account.edit_profile": "Edit profile",
"account.endorse": "Feature on profile",
"account.follow": "Follow",
@ -57,7 +57,7 @@
"column.community": "Local timeline",
"column.direct": "Direct messages",
"column.directory": "Browse profiles",
"column.domain_blocks": "Hidden domains",
"column.domain_blocks": "Blocked domains",
"column.favourites": "Favourites",
"column.follow_requests": "Follow requests",
"column.home": "Home",
@ -107,7 +107,7 @@
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.domain_block.confirm": "Block entire domain",
"confirmations.domain_block.message": "Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.",
"confirmations.logout.confirm": "Log out",
"confirmations.logout.message": "Are you sure you want to log out?",
@ -150,7 +150,7 @@
"empty_column.bookmarked_statuses": "You don't have any bookmarked toots yet. When you bookmark one, it will show up here.",
"empty_column.community": "The local timeline is empty. Write something publicly to get the ball rolling!",
"empty_column.direct": "You don't have any direct messages yet. When you send or receive one, it will show up here.",
"empty_column.domain_blocks": "There are no hidden domains yet.",
"empty_column.domain_blocks": "There are no blocked domains yet.",
"empty_column.favourited_statuses": "You don't have any favourite toots yet. When you favourite one, it will show up here.",
"empty_column.favourites": "No one has favourited this toot yet. When someone does, they will show up here.",
"empty_column.follow_requests": "You don't have any follow requests yet. When you receive one, it will show up here.",
@ -269,7 +269,7 @@
"navigation_bar.compose": "Compose new toot",
"navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains",
"navigation_bar.domain_blocks": "Blocked domains",
"navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words",

@ -1,4 +1,4 @@
import Rails from 'rails-ujs';
import Rails from '@rails/ujs';
export const logOut = () => {
const form = document.createElement('form');

@ -8,7 +8,7 @@ start();
function main() {
const IntlMessageFormat = require('intl-messageformat').default;
const { timeAgoString } = require('../mastodon/components/relative_timestamp');
const { delegate } = require('rails-ujs');
const { delegate } = require('@rails/ujs');
const emojify = require('../mastodon/features/emoji/emoji').default;
const { getLocale } = require('../mastodon/locales');
const { messages } = getLocale();

@ -52,8 +52,10 @@ class LanguageDetector
def detect_language_code(text)
return if unreliable_input?(text)
result = @identifier.find_language(text)
iso6391(result.language.to_s).to_sym if result.reliable?
iso6391(result.language.to_s).to_sym if result&.reliable?
end
def iso6391(bcp47)

@ -46,6 +46,7 @@
# silenced_at :datetime
# suspended_at :datetime
# trust_level :integer
# hide_collections :boolean
#
class Account < ApplicationRecord
@ -325,6 +326,14 @@ class Account < ApplicationRecord
save!
end
def hides_followers?
hide_collections? || user_hides_network?
end
def hides_following?
hide_collections? || user_hides_network?
end
def object_type
:person
end

@ -8,8 +8,11 @@
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# title :string default(""), not null
#
class AccountWarningPreset < ApplicationRecord
validates :text, presence: true
scope :alphabetic, -> { order(title: :asc, text: :asc) }
end

@ -62,8 +62,6 @@ class Admin::AccountAction
def process_action!
case type
when 'none'
handle_resolve!
when 'disable'
handle_disable!
when 'silence'
@ -105,16 +103,6 @@ class Admin::AccountAction
end
end
def handle_resolve!
if with_report? && report.account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
# This is an automated report and it is being dismissed, so it's
# a false positive, in which case update the account's trust level
# to prevent further spam checks
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
end
end
def handle_disable!
authorize(target_account.user, :disable?)
log_action(:disable, target_account.user)

@ -74,7 +74,7 @@ module Attachmentable
self.class.attachment_definitions.each_key do |attachment_name|
attachment = send(attachment_name)
next if attachment.blank? || attachment.queued_for_write[:original].blank?
next if attachment.blank? || attachment.queued_for_write[:original].blank? || attachment.options[:preserve_files]
attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name))
end

@ -7,13 +7,27 @@
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# parent_id :bigint(8)
#
class EmailDomainBlock < ApplicationRecord
include DomainNormalizable
belongs_to :parent, class_name: 'EmailDomainBlock', optional: true
has_many :children, class_name: 'EmailDomainBlock', foreign_key: :parent_id, inverse_of: :parent, dependent: :destroy
validates :domain, presence: true, uniqueness: true, domain: true
def with_dns_records=(val)
@with_dns_records = ActiveModel::Type::Boolean.new.cast(val)
end
def with_dns_records?
@with_dns_records
end
alias with_dns_records with_dns_records?
def self.block?(email)
_, domain = email.split('@', 2)

@ -19,12 +19,14 @@
# description :text
# scheduled_status_id :bigint(8)
# blurhash :string
# processing :integer
#
class MediaAttachment < ApplicationRecord
self.inheritance_column = nil
enum type: [:image, :gifv, :video, :unknown, :audio]
enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true
MAX_DESCRIPTION_LENGTH = 1_500
@ -55,6 +57,43 @@ class MediaAttachment < ApplicationRecord
},
}.freeze
VIDEO_FORMAT = {
format: 'mp4',
content_type: 'video/mp4',
convert_options: {
output: {
'loglevel' => 'fatal',
'movflags' => 'faststart',
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'c:v' => 'h264',
'maxrate' => '1300K',
'bufsize' => '1300K',
'frames:v' => 60 * 60 * 3,
'crf' => 18,
'map_metadata' => '-1',
},
},
}.freeze
VIDEO_PASSTHROUGH_OPTIONS = {
video_codecs: ['h264'],
audio_codecs: ['aac', nil],
colorspaces: ['yuv420p'],
options: {
format: 'mp4',
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'c:v' => 'copy',
'c:a' => 'copy',
},
},
},
}.freeze
VIDEO_STYLES = {
small: {
convert_options: {
@ -69,17 +108,7 @@ class MediaAttachment < ApplicationRecord
blurhash: BLURHASH_OPTIONS,
},
original: {
keep_same_format: true,
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'c:v' => 'copy',
'c:a' => 'copy',
},
},
},
original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS),
}.freeze
AUDIO_STYLES = {
@ -96,26 +125,6 @@ class MediaAttachment < ApplicationRecord
},
}.freeze
VIDEO_FORMAT = {
format: 'mp4',
content_type: 'video/mp4',
convert_options: {
output: {
'loglevel' => 'fatal',
'movflags' => 'faststart',
'pix_fmt' => 'yuv420p',
'vf' => 'scale=\'trunc(iw/2)*2:trunc(ih/2)*2\'',
'vsync' => 'cfr',
'c:v' => 'h264',
'maxrate' => '1300K',
'bufsize' => '1300K',
'frames:v' => 60 * 60 * 3,
'crf' => 18,
'map_metadata' => '-1',
},
},
}.freeze
VIDEO_CONVERTED_STYLES = {
small: VIDEO_STYLES[:small],
original: VIDEO_FORMAT,
@ -124,6 +133,9 @@ class MediaAttachment < ApplicationRecord
IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.megabytes).to_i
MAX_VIDEO_MATRIX_LIMIT = 2_304_000 # 1920x1200px
MAX_VIDEO_FRAME_RATE = 60
belongs_to :account, inverse_of: :media_attachments, optional: true
belongs_to :status, inverse_of: :media_attachments, optional: true
belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
@ -156,6 +168,10 @@ class MediaAttachment < ApplicationRecord
remote_url.blank?
end
def not_processed?
processing.present? && !processing_complete?
end
def needs_redownload?
file.blank? && remote_url.present?
end
@ -203,12 +219,21 @@ class MediaAttachment < ApplicationRecord
"#{x},#{y}"
end
attr_writer :delay_processing
def delay_processing?
@delay_processing
end
after_commit :enqueue_processing, on: :create
after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local?
before_create :set_shortcode
before_create :set_processing
before_post_process :set_type_and_extension
before_post_process :check_video_dimensions
before_save :set_meta
@ -277,6 +302,21 @@ class MediaAttachment < ApplicationRecord
end
end
def set_processing
self.processing = delay_processing? ? :queued : :complete
end
def check_video_dimensions
return unless (video? || gifv?) && file.queued_for_write[:original].present?
movie = FFMPEG::Movie.new(file.queued_for_write[:original].path)
return unless movie.valid?
raise Mastodon::DimensionsValidationError, "#{movie.width}x#{movie.height} videos are not supported" if movie.width * movie.height > MAX_VIDEO_MATRIX_LIMIT
raise Mastodon::DimensionsValidationError, "#{movie.frame_rate.to_i}fps videos are not supported" if movie.frame_rate > MAX_VIDEO_FRAME_RATE
end
def set_meta
meta = populate_meta
@ -322,9 +362,11 @@ class MediaAttachment < ApplicationRecord
}.compact
end
def reset_parent_cache
return if status_id.nil?
def enqueue_processing
PostProcessMediaWorker.perform_async(id) if delay_processing?
end
Rails.cache.delete("statuses/#{status_id}")
def reset_parent_cache
Rails.cache.delete("statuses/#{status_id}") if status_id.present?
end
end

@ -59,6 +59,14 @@ class Report < ApplicationRecord
end
def resolve!(acting_account)
if account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
# This is an automated report and it is being dismissed, so it's
# a false positive, in which case update the account's trust level
# to prevent further spam checks
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
end
RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] }
update!(action_taken: true, action_taken_by_account_id: acting_account.id)
end

@ -148,10 +148,12 @@ class Status < ApplicationRecord
ids += mentions.where(account: Account.local).pluck(:account_id)
ids += favourites.where(account: Account.local).pluck(:account_id)
ids += reblogs.where(account: Account.local).pluck(:account_id)
ids += bookmarks.where(account: Account.local).pluck(:account_id)
else
ids += preloaded.mentions[id] || []
ids += preloaded.favourites[id] || []
ids += preloaded.reblogs[id] || []
ids += preloaded.bookmarks[id] || []
end
ids.uniq

@ -12,7 +12,9 @@ class REST::MediaAttachmentSerializer < ActiveModel::Serializer
end
def url
if object.needs_redownload?
if object.not_processed?
nil
elsif object.needs_redownload?
media_proxy_url(object.id, :original)
else
full_asset_url(object.file.url(:original))

@ -94,6 +94,7 @@ class ActivityPub::ProcessAccountService < BaseService
@account.statuses_count = outbox_total_items if outbox_total_items.present?
@account.following_count = following_total_items if following_total_items.present?
@account.followers_count = followers_total_items if followers_total_items.present?
@account.hide_collections = following_private? || followers_private?
@account.moved_to_account = @json['movedTo'].present? ? moved_account : nil
end
@ -166,26 +167,36 @@ class ActivityPub::ProcessAccountService < BaseService
end
def outbox_total_items
collection_total_items('outbox')
collection_info('outbox').first
end
def following_total_items
collection_total_items('following')
collection_info('following').first
end
def followers_total_items
collection_total_items('followers')
collection_info('followers').first
end
def collection_total_items(type)
return if @json[type].blank?
def following_private?
!collection_info('following').last
end
def followers_private?
!collection_info('followers').last
end
def collection_info(type)
return [nil, nil] if @json[type].blank?
return @collections[type] if @collections.key?(type)
collection = fetch_resource_without_id_validation(@json[type])
@collections[type] = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil
total_items = collection.is_a?(Hash) && collection['totalItems'].present? && collection['totalItems'].is_a?(Numeric) ? collection['totalItems'] : nil
has_first_page = collection.is_a?(Hash) && collection['first'].present?
@collections[type] = [total_items, has_first_page]
rescue HTTP::Error, OpenSSL::SSL::SSLError
@collections[type] = nil
@collections[type] = [nil, nil]
end
def moved_account

@ -5,6 +5,8 @@ class FetchResourceService < BaseService
ACCEPT_HEADER = 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams", text/html;q=0.1'
attr_reader :response_code
def call(url)
return if url.blank?
@ -27,6 +29,7 @@ class FetchResourceService < BaseService
end
def process_response(response, terminal = false)
@response_code = response.code
return nil if response.code != 200
if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)

@ -110,6 +110,7 @@ class PostStatusService < BaseService
@media = @account.media_attachments.where(status_id: nil).where(id: @options[:media_ids].take(4).map(&:to_i))
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.images_and_video') if @media.size > 1 && @media.find(&:audio_or_video?)
raise Mastodon::ValidationError, I18n.t('media_attachments.validations.not_ready') if @media.any?(&:not_processed?)
end
def language_from_option(str)

@ -12,7 +12,7 @@ class ResolveURLService < BaseService
process_local_url
elsif !fetched_resource.nil?
process_url
elsif @on_behalf_of.present?
else
process_url_from_db
end
end
@ -30,6 +30,8 @@ class ResolveURLService < BaseService
end
def process_url_from_db
return unless @on_behalf_of.present? && [401, 403, 404].include?(fetch_resource_service.response_code)
# It may happen that the resource is a private toot, and thus not fetchable,
# but we can return the toot if we already know about it.
status = Status.find_by(uri: @url) || Status.find_by(url: @url)
@ -40,7 +42,11 @@ class ResolveURLService < BaseService
end
def fetched_resource
@fetched_resource ||= FetchResourceService.new.call(@url)
@fetched_resource ||= fetch_resource_service.call(@url)
end
def fetch_resource_service
@_fetch_resource_service ||= FetchResourceService.new
end
def resource_url

@ -21,7 +21,7 @@
- unless @warning_presets.empty?
.fields-group
= f.input :warning_preset_id, collection: @warning_presets, label_method: :text, wrapper: :with_block_label
= f.input :warning_preset_id, collection: @warning_presets, label_method: ->(warning_preset) { [warning_preset.title.presence, truncate(warning_preset.text)].compact.join(' - ') }, wrapper: :with_block_label
.fields-group
= f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path)

@ -96,10 +96,17 @@
= table_link_to 'angle-double-down', t('admin.accounts.demote'), demote_admin_account_role_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:demote, @account.user)
%tr
%th= t('admin.accounts.email')
%td= @account.user_email
%th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
%td{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= @account.user_email
%td= table_link_to 'edit', t('admin.accounts.change_email.label'), admin_account_change_email_path(@account.id) if can?(:change_email, @account.user)
%tr
%td= table_link_to 'search', t('admin.accounts.search_same_email_domain'), admin_accounts_path(email: "%@#{@account.user_email.split('@').last}")
- if can?(:create, :email_domain_block)
%tr
%td= table_link_to 'ban', t('admin.accounts.add_email_domain_block'), new_admin_email_domain_block_path(_domain: @account.user_email.split('@').last)
- if @account.user_unconfirmed_email.present?
%tr
%th= t('admin.accounts.unconfirmed_email')
@ -204,7 +211,7 @@
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@account.id, type: 'suspend'), class: 'button button--destructive' if can?(:suspend, @account)
- unless @account.local?
- if DomainBlock.where(domain: @account.domain).exists?
- if DomainBlock.rule_for(@account.domain)
= link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
- else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'

@ -3,3 +3,13 @@
%samp= email_domain_block.domain
%td
= table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(email_domain_block), method: :delete
- email_domain_block.children.each do |child_email_domain_block|
%tr
%td
%samp= child_email_domain_block.domain
%span.muted-hint
= surround '(', ')' do
= t('admin.email_domain_blocks.from_html', domain: content_tag(:samp, email_domain_block.domain))
%td
= table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(child_email_domain_block), method: :delete

@ -5,7 +5,10 @@
= render 'shared/error_messages', object: @email_domain_block
.fields-group
= f.input :domain, wrapper: :with_label, label: t('admin.email_domain_blocks.domain')
= f.input :domain, wrapper: :with_block_label, label: t('admin.email_domain_blocks.domain')
.fields-group
= f.input :with_dns_records, as: :boolean, wrapper: :with_label
.actions
= f.button :button, t('.create'), type: :submit

@ -0,0 +1,10 @@
.announcements-list__item
= link_to edit_admin_warning_preset_path(warning_preset), class: 'announcements-list__item__title' do
= warning_preset.title.presence || truncate(warning_preset.text)
.announcements-list__item__action-bar
.announcements-list__item__meta
= truncate(warning_preset.text)
%div
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(warning_preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') } if can?(:destroy, warning_preset)

@ -4,6 +4,9 @@
= simple_form_for @warning_preset, url: admin_warning_preset_path(@warning_preset) do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :title, wrapper: :with_block_label
.fields-group
= f.input :text, wrapper: :with_block_label

@ -5,6 +5,9 @@
= simple_form_for @warning_preset, url: admin_warning_presets_path do |f|
= render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :title, wrapper: :with_block_label
.fields-group
= f.input :text, wrapper: :with_block_label
@ -13,18 +16,9 @@
%hr.spacer/
- unless @warning_presets.empty?
.table-wrapper
%table.table
%thead
%tr
%th= t('simple_form.labels.account_warning_preset.text')
%th
%tbody
- @warning_presets.each do |preset|
%tr
%td
= Formatter.instance.linkify(preset.text)
%td
= table_link_to 'pencil', t('admin.warning_presets.edit'), edit_admin_warning_preset_path(preset)
= table_link_to 'trash', t('admin.warning_presets.delete'), admin_warning_preset_path(preset), method: :delete, data: { confirm: t('admin.accounts.are_you_sure') }
- if @warning_presets.empty?
%div.muted-hint.center-text
= t 'admin.warning_presets.empty'
- else
.announcements-list
= render partial: 'warning_preset', collection: @warning_presets

@ -9,8 +9,12 @@ class BackupWorker
backup_id = msg['args'].first
ActiveRecord::Base.connection_pool.with_connection do
backup = Backup.find(backup_id)
backup&.destroy
begin
backup = Backup.find(backup_id)
backup.destroy
rescue ActiveRecord::RecordNotFound
true
end
end
end

@ -0,0 +1,34 @@
# frozen_string_literal: true
class PostProcessMediaWorker
include Sidekiq::Worker
sidekiq_options retry: 1, dead: false
sidekiq_retries_exhausted do |msg|
media_attachment_id = msg['args'].first
ActiveRecord::Base.connection_pool.with_connection do
begin
media_attachment = MediaAttachment.find(media_attachment_id)
media_attachment.processing = :failed
media_attachment.save
rescue ActiveRecord::RecordNotFound
true
end
end
Sidekiq.logger.error("Processing media attachment #{media_attachment_id} failed with #{msg['error_message']}")
end
def perform(media_attachment_id)
media_attachment = MediaAttachment.find(media_attachment_id)
media_attachment.processing = :in_progress
media_attachment.save
media_attachment.file.reprocess_original!
media_attachment.processing = :complete
media_attachment.save
rescue ActiveRecord::RecordNotFound
true
end
end

@ -7,6 +7,7 @@ require 'rails/all'
Bundler.require(*Rails.groups)
require_relative '../app/lib/exceptions'
require_relative '../lib/paperclip/attachment_extensions'
require_relative '../lib/paperclip/lazy_thumbnail'
require_relative '../lib/paperclip/gif_transcoder'
require_relative '../lib/paperclip/video_transcoder'

@ -1,6 +1,6 @@
# frozen_string_literal: true
lock '3.11.2'
lock '3.12.1'
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
set :branch, ENV.fetch('BRANCH', 'master')

@ -19,4 +19,4 @@ Sidekiq.configure_client do |config|
config.redis = redis_params
end
Sidekiq::Logging.logger.level = ::Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s)
Sidekiq.logger.level = ::Logger.const_get(ENV.fetch('RAILS_LOG_LEVEL', 'info').upcase.to_s)

@ -562,7 +562,6 @@ ar:
warning_presets:
add_new: إضافة واحد جديد
delete: حذف
edit: تعديل
edit_preset: تعديل نموذج التحذير
title: إدارة نماذج التحذير
admin_mailer:

@ -573,7 +573,6 @@ ca:
warning_presets:
add_new: Afegeix-ne un de nou
delete: Esborra
edit: Edita
edit_preset: Edita l'avís predeterminat
title: Gestiona les configuracions predefinides dels avisos
admin_mailer:

@ -573,7 +573,6 @@ co:
warning_presets:
add_new: Aghjunghje
delete: Sguassà
edit: Cambià
edit_preset: Cambià a preselezzione d'avertimentu
title: Amministrà e preselezzione d'avertimentu
admin_mailer:

@ -588,7 +588,6 @@ cs:
warning_presets:
add_new: Přidat nové
delete: Smazat
edit: Upravit
edit_preset: Upravit předlohu pro varování
title: Spravovat předlohy pro varování
admin_mailer:

@ -605,7 +605,6 @@ cy:
warning_presets:
add_new: Ychwanegu newydd
delete: Dileu
edit: Golygu
edit_preset: Golygu rhagosodiad rhybudd
title: Rheoli rhagosodiadau rhybudd
admin_mailer:

@ -489,7 +489,6 @@ da:
most_recent: Seneste
warning_presets:
delete: Slet
edit: Rediger
admin_mailer:
new_report:
body: "%{reporter} har anmeldt %{target}"

@ -573,7 +573,6 @@ de:
warning_presets:
add_new: Neu hinzufügen
delete: Löschen
edit: Bearbeiten
edit_preset: Warnungsvorlage bearbeiten
title: Warnungsvorlagen verwalten
admin_mailer:

@ -573,7 +573,6 @@ el:
warning_presets:
add_new: Πρόσθεση νέου
delete: Διαγραφή
edit: Ενημέρωση
edit_preset: Ενημέρωση προκαθορισμένης προειδοποίησης
title: Διαχείριση προκαθορισμένων προειδοποιήσεων
admin_mailer:

@ -92,6 +92,7 @@ en:
delete: Delete
destroyed_msg: Moderation note successfully destroyed!
accounts:
add_email_domain_block: Blacklist e-mail domain
approve: Approve
approve_all: Approve all
are_you_sure: Are you sure?
@ -172,6 +173,7 @@ en:
staff: Staff
user: User
search: Search
search_same_email_domain: Other users with the same e-mail domain
search_same_ip: Other users with the same IP
shared_inbox_url: Shared inbox URL
show:
@ -359,6 +361,7 @@ en:
destroyed_msg: Successfully deleted e-mail domain from blacklist
domain: Domain
empty: No e-mail domains currently blacklisted.
from_html: from %{domain}
new:
create: Add domain
title: New e-mail blacklist entry
@ -589,7 +592,6 @@ en:
warning_presets:
add_new: Add new
delete: Delete
edit: Edit
edit_preset: Edit warning preset
title: Manage warning presets
admin_mailer:
@ -867,6 +869,7 @@ en:
media_attachments:
validations:
images_and_video: Cannot attach a video to a status that already contains images
not_ready: Cannot attach files that have not finished processing. Try again in a moment!
too_many: Cannot attach more than 4 files
migrations:
acct: Moved to

@ -474,7 +474,6 @@ en_GB:
warning_presets:
add_new: Add new
delete: Delete
edit: Edit
edit_preset: Edit warning preset
title: Manage warning presets
admin_mailer:

@ -557,7 +557,6 @@ eo:
warning_presets:
add_new: Aldoni novan
delete: Forigi
edit: Redakti
edit_preset: Redakti avertan antaŭagordon
title: Administri avertajn antaŭagordojn
admin_mailer:

@ -573,7 +573,6 @@ es-AR:
warning_presets:
add_new: Agregar nuevo
delete: Eliminar
edit: Editar
edit_preset: Editar preajuste de advertencia
title: Administrar preajustes de advertencia
admin_mailer:

@ -573,7 +573,6 @@ es:
warning_presets:
add_new: Añadir nuevo
delete: Borrar
edit: Editar
edit_preset: Editar aviso predeterminado
title: Editar configuración predeterminada de avisos
admin_mailer:

@ -576,7 +576,6 @@ et:
warning_presets:
add_new: Lisa uus
delete: Kustuta
edit: Redigeeri
edit_preset: Redigeeri hoiatuse eelseadistust
title: Halda hoiatuste eelseadistusi
admin_mailer:

@ -573,7 +573,6 @@ eu:
warning_presets:
add_new: Gehitu berria
delete: Ezabatu
edit: Editatu
edit_preset: Editatu abisu aurre-ezarpena
title: Kudeatu abisu aurre-ezarpenak
admin_mailer:

@ -575,7 +575,6 @@ fa:
warning_presets:
add_new: افزودن تازه
delete: زدودن
edit: ویرایش
edit_preset: ویرایش هشدار پیش‌فرض
title: مدیریت هشدارهای پیش‌فرض
admin_mailer:

@ -573,7 +573,6 @@ fr:
warning_presets:
add_new: Ajouter un nouveau
delete: Effacer
edit: Éditer
edit_preset: Éditer les avertissements prédéfinis
title: Gérer les avertissements prédéfinis
admin_mailer:

@ -573,7 +573,6 @@ gl:
warning_presets:
add_new: Engadir novo
delete: Eliminar
edit: Editar
edit_preset: Editar aviso preestablecido
title: Xestionar avisos preestablecidos
admin_mailer:

@ -575,7 +575,6 @@ hu:
warning_presets:
add_new: Új hozzáadása
delete: Törlés
edit: Szerkesztés
edit_preset: Figyelmeztetés szerkesztése
title: Figyelmeztetések
admin_mailer:

@ -565,7 +565,6 @@ id:
warning_presets:
add_new: Tambah baru
delete: Hapus
edit: Sunting
edit_preset: Sunting preset peringatan
title: Kelola preset peringatan
admin_mailer:

@ -573,7 +573,6 @@ is:
warning_presets:
add_new: Bæta við nýju
delete: Eyða
edit: Breyta
edit_preset: Breyta forstilltri aðvörun
title: Sýsla með forstilltar aðvaranir
admin_mailer:

@ -573,7 +573,6 @@ it:
warning_presets:
add_new: Aggiungi nuovo
delete: Cancella
edit: Modifica
edit_preset: Modifica avviso predefinito
title: Gestisci avvisi predefiniti
admin_mailer:

@ -565,7 +565,6 @@ ja:
warning_presets:
add_new: 追加
delete: 削除
edit: 編集
edit_preset: プリセット警告文を編集
title: プリセット警告文を管理
admin_mailer:

@ -330,7 +330,6 @@ kab:
warning_presets:
add_new: Rnu amaynut
delete: Kkes
edit: Ẓreg
admin_mailer:
new_report:
subject: Aneqqis amaynut i %{instance} (#%{id})

@ -555,7 +555,6 @@ kk:
warning_presets:
add_new: Add nеw
delete: Deletе
edit: Еdit
edit_preset: Edit warning prеset
title: Manage warning presеts
admin_mailer:

@ -567,7 +567,6 @@ ko:
warning_presets:
add_new: 새로 추가
delete: 삭제
edit: 편집
edit_preset: 경고 틀 수정
title: 경고 틀 관리
admin_mailer:

@ -410,7 +410,6 @@ lt:
warning_presets:
add_new: Pridėti naują
delete: Ištrinti
edit: Keisti
edit_preset: Keisti įspėjimo nustatymus
title: Valdyti įspėjimo nustatymus
admin_mailer:

@ -573,7 +573,6 @@ nl:
warning_presets:
add_new: Nieuwe toevoegen
delete: Verwijderen
edit: Bewerken
edit_preset: Voorinstelling van waarschuwing bewerken
title: Voorinstellingen van waarschuwingen beheren
admin_mailer:

@ -566,7 +566,6 @@ nn:
warning_presets:
add_new: Legg til ny
delete: Slett
edit: Rediger
edit_preset: Endr åtvaringsoppsett
title: Handsam åtvaringsoppsett
admin_mailer:

@ -561,7 +561,6 @@
warning_presets:
add_new: Legg til ny
delete: Slett
edit: Rediger
admin_mailer:
new_pending_account:
body: Detaljer om den nye kontoen er nedenfor. Du kan godkjenne eller avvise denne søknaden.

@ -558,7 +558,6 @@ oc:
warning_presets:
add_new: Najustar un nòu
delete: Escafar
edit: Modificar
edit_preset: Modificar lo tèxt predefinit davertiment
title: Gerir los tèxtes predefinits
admin_mailer:

@ -541,7 +541,6 @@ pl:
warning_presets:
add_new: Dodaj nowy
delete: Usuń
edit: Edytuj
edit_preset: Edytuj szablon ostrzeżenia
title: Zarządzaj szablonami ostrzeżeń
admin_mailer:

@ -573,7 +573,6 @@ pt-BR:
warning_presets:
add_new: Adicionar novo
delete: Excluir
edit: Editar
edit_preset: Editar o aviso pré-definido
title: Gerenciar os avisos pré-definidos
admin_mailer:

@ -573,7 +573,6 @@ pt-PT:
warning_presets:
add_new: Adicionar novo
delete: Apagar
edit: Editar
edit_preset: Editar o aviso predefinido
title: Gerir os avisos predefinidos
admin_mailer:

@ -594,7 +594,6 @@ ru:
warning_presets:
add_new: Добавить
delete: Удалить
edit: Изменить
edit_preset: Удалить шаблон предупреждения
title: Управление шаблонами предупреждений
admin_mailer:

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save