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

Merge upstream changes
This commit is contained in:
ThibG 2020-03-22 17:59:47 +01:00 committed by GitHub
commit d101438b9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
125 changed files with 1017 additions and 355 deletions

View File

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

View File

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

View File

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

View File

@ -47,6 +47,11 @@ class StatusesIndex < Chewy::Index
data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) } data.each.with_object({}) { |(id, name), result| (result[id] ||= []).push(name) }
end 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 root date_detection: false do
field :id, type: 'long' field :id, type: 'long'
field :account_id, type: 'long' field :account_id, type: 'long'

View File

@ -6,12 +6,12 @@ module Admin
def index def index
authorize :email_domain_block, :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 end
def new def new
authorize :email_domain_block, :create? authorize :email_domain_block, :create?
@email_domain_block = EmailDomainBlock.new @email_domain_block = EmailDomainBlock.new(domain: params[:_domain])
end end
def create def create
@ -21,6 +21,28 @@ module Admin
if @email_domain_block.save if @email_domain_block.save
log_action :create, @email_domain_block 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') redirect_to admin_email_domain_blocks_path, notice: I18n.t('admin.email_domain_blocks.created_msg')
else else
render :new render :new
@ -41,7 +63,7 @@ module Admin
end end
def resource_params def resource_params
params.require(:email_domain_block).permit(:domain) params.require(:email_domain_block).permit(:domain, :with_dns_records)
end end
end end
end end

View File

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

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

@ -25,7 +25,7 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
end end
def hide_results? 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 end
def default_accounts def default_accounts

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
// This file will be loaded on admin pages, regardless of theme. // 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'; import ready from '../mastodon/ready';
const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]'; const batchCheckboxClassName = '.batch-checkbox input[type="checkbox"]';

View File

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

View File

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

View File

@ -259,12 +259,31 @@ export function uploadCompose(files) {
// Account for disparity in size of original image and resized data // Account for disparity in size of original image and resized data
total += file.size - f.size; total += file.size - f.size;
return api(getState).post('/api/v1/media', data, { return api(getState).post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){ onUploadProgress: function({ loaded }){
progress[i] = loaded; progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); 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))); }).catch(error => dispatch(uploadComposeFail(error)));
}; };
}; };

View File

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

View File

@ -6,7 +6,7 @@ import Domain from '../components/domain';
import { openModal } from '../actions/modal'; import { openModal } from '../actions/modal';
const messages = defineMessages({ 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 = () => { const makeMapStateToProps = () => {

View File

@ -40,7 +40,7 @@ const messages = defineMessages({
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, 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' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t 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'])) { if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>); info.push(<span className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>);
} else if (me !== account.get('id') && account.getIn(['relationship', 'domain_blocking'])) { } 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')) { if (me !== account.get('id')) {

View File

@ -13,8 +13,8 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
import ScrollableList from 'flavours/glitch/components/scrollable_list'; import ScrollableList from 'flavours/glitch/components/scrollable_list';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
}); });
const mapStateToProps = state => ({ 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 ( return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -230,12 +230,31 @@ export function uploadCompose(files) {
// Account for disparity in size of original image and resized data // Account for disparity in size of original image and resized data
total += file.size - f.size; total += file.size - f.size;
return api(getState).post('/api/v1/media', data, { return api(getState).post('/api/v2/media', data, {
onUploadProgress: function({ loaded }){ onUploadProgress: function({ loaded }){
progress[i] = loaded; progress[i] = loaded;
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); 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))); }).catch(error => dispatch(uploadComposeFail(error)));
}; };
}; };

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import Domain from '../components/domain';
import { openModal } from '../actions/modal'; import { openModal } from '../actions/modal';
const messages = defineMessages({ 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 = () => { const makeMapStateToProps = () => {

View File

@ -39,7 +39,7 @@ const messages = defineMessages({
favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' }, favourites: { id: 'navigation_bar.favourites', defaultMessage: 'Favourites' },
lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' }, lists: { id: 'navigation_bar.lists', defaultMessage: 'Lists' },
blocks: { id: 'navigation_bar.blocks', defaultMessage: 'Blocked users' }, 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' }, mutes: { id: 'navigation_bar.mutes', defaultMessage: 'Muted users' },
endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' }, endorse: { id: 'account.endorse', defaultMessage: 'Feature on profile' },
unendorse: { id: 'account.unendorse', defaultMessage: 'Don\'t 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'])) { if (me !== account.get('id') && account.getIn(['relationship', 'muting'])) {
info.push(<span key='muted' className='relationship-tag'><FormattedMessage id='account.muted' defaultMessage='Muted' /></span>); 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'])) { } 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')) { if (me !== account.get('id')) {

View File

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

View File

@ -13,8 +13,8 @@ import { fetchDomainBlocks, expandDomainBlocks } from '../../actions/domain_bloc
import ScrollableList from '../../components/scrollable_list'; import ScrollableList from '../../components/scrollable_list';
const messages = defineMessages({ const messages = defineMessages({
heading: { id: 'column.domain_blocks', defaultMessage: 'Hidden domains' }, heading: { id: 'column.domain_blocks', defaultMessage: 'Blocked domains' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
}); });
const mapStateToProps = state => ({ 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 ( return (
<Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}> <Column bindToDocument={!multiColumn} icon='minus-circle' heading={intl.formatMessage(messages.heading)}>

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
"account.blocked": "Blocked", "account.blocked": "Blocked",
"account.cancel_follow_request": "Cancel follow request", "account.cancel_follow_request": "Cancel follow request",
"account.direct": "Direct message @{name}", "account.direct": "Direct message @{name}",
"account.domain_blocked": "Domain hidden", "account.domain_blocked": "Domain blocked",
"account.edit_profile": "Edit profile", "account.edit_profile": "Edit profile",
"account.endorse": "Feature on profile", "account.endorse": "Feature on profile",
"account.follow": "Follow", "account.follow": "Follow",
@ -57,7 +57,7 @@
"column.community": "Local timeline", "column.community": "Local timeline",
"column.direct": "Direct messages", "column.direct": "Direct messages",
"column.directory": "Browse profiles", "column.directory": "Browse profiles",
"column.domain_blocks": "Hidden domains", "column.domain_blocks": "Blocked domains",
"column.favourites": "Favourites", "column.favourites": "Favourites",
"column.follow_requests": "Follow requests", "column.follow_requests": "Follow requests",
"column.home": "Home", "column.home": "Home",
@ -107,7 +107,7 @@
"confirmations.delete.message": "Are you sure you want to delete this status?", "confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Delete", "confirmations.delete_list.confirm": "Delete",
"confirmations.delete_list.message": "Are you sure you want to permanently delete this list?", "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.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.confirm": "Log out",
"confirmations.logout.message": "Are you sure you want to 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.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.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.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.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.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.", "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.compose": "Compose new toot",
"navigation_bar.direct": "Direct messages", "navigation_bar.direct": "Direct messages",
"navigation_bar.discover": "Discover", "navigation_bar.discover": "Discover",
"navigation_bar.domain_blocks": "Hidden domains", "navigation_bar.domain_blocks": "Blocked domains",
"navigation_bar.edit_profile": "Edit profile", "navigation_bar.edit_profile": "Edit profile",
"navigation_bar.favourites": "Favourites", "navigation_bar.favourites": "Favourites",
"navigation_bar.filters": "Muted words", "navigation_bar.filters": "Muted words",

View File

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

View File

@ -8,7 +8,7 @@ start();
function main() { function main() {
const IntlMessageFormat = require('intl-messageformat').default; const IntlMessageFormat = require('intl-messageformat').default;
const { timeAgoString } = require('../mastodon/components/relative_timestamp'); 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 emojify = require('../mastodon/features/emoji/emoji').default;
const { getLocale } = require('../mastodon/locales'); const { getLocale } = require('../mastodon/locales');
const { messages } = getLocale(); const { messages } = getLocale();

View File

@ -52,8 +52,10 @@ class LanguageDetector
def detect_language_code(text) def detect_language_code(text)
return if unreliable_input?(text) return if unreliable_input?(text)
result = @identifier.find_language(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 end
def iso6391(bcp47) def iso6391(bcp47)

View File

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

View File

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

View File

@ -62,8 +62,6 @@ class Admin::AccountAction
def process_action! def process_action!
case type case type
when 'none'
handle_resolve!
when 'disable' when 'disable'
handle_disable! handle_disable!
when 'silence' when 'silence'
@ -105,16 +103,6 @@ class Admin::AccountAction
end end
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! def handle_disable!
authorize(target_account.user, :disable?) authorize(target_account.user, :disable?)
log_action(:disable, target_account.user) log_action(:disable, target_account.user)

View File

@ -74,7 +74,7 @@ module Attachmentable
self.class.attachment_definitions.each_key do |attachment_name| self.class.attachment_definitions.each_key do |attachment_name|
attachment = send(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)) attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name))
end end

View File

@ -7,13 +7,27 @@
# domain :string default(""), not null # domain :string default(""), not null
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# parent_id :bigint(8)
# #
class EmailDomainBlock < ApplicationRecord class EmailDomainBlock < ApplicationRecord
include DomainNormalizable 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 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) def self.block?(email)
_, domain = email.split('@', 2) _, domain = email.split('@', 2)

View File

@ -19,12 +19,14 @@
# description :text # description :text
# scheduled_status_id :bigint(8) # scheduled_status_id :bigint(8)
# blurhash :string # blurhash :string
# processing :integer
# #
class MediaAttachment < ApplicationRecord class MediaAttachment < ApplicationRecord
self.inheritance_column = nil self.inheritance_column = nil
enum type: [:image, :gifv, :video, :unknown, :audio] enum type: [:image, :gifv, :video, :unknown, :audio]
enum processing: [:queued, :in_progress, :complete, :failed], _prefix: true
MAX_DESCRIPTION_LENGTH = 1_500 MAX_DESCRIPTION_LENGTH = 1_500
@ -55,47 +57,6 @@ class MediaAttachment < ApplicationRecord
}, },
}.freeze }.freeze
VIDEO_STYLES = {
small: {
convert_options: {
output: {
'loglevel' => 'fatal',
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
},
},
format: 'png',
time: 0,
file_geometry_parser: FastGeometryParser,
blurhash: BLURHASH_OPTIONS,
},
original: {
keep_same_format: true,
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'c:v' => 'copy',
'c:a' => 'copy',
},
},
},
}.freeze
AUDIO_STYLES = {
original: {
format: 'mp3',
content_type: 'audio/mpeg',
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'q:a' => 2,
},
},
},
}.freeze
VIDEO_FORMAT = { VIDEO_FORMAT = {
format: 'mp4', format: 'mp4',
content_type: 'video/mp4', content_type: 'video/mp4',
@ -116,6 +77,54 @@ class MediaAttachment < ApplicationRecord
}, },
}.freeze }.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: {
output: {
'loglevel' => 'fatal',
vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
},
},
format: 'png',
time: 0,
file_geometry_parser: FastGeometryParser,
blurhash: BLURHASH_OPTIONS,
},
original: VIDEO_FORMAT.merge(passthrough_options: VIDEO_PASSTHROUGH_OPTIONS),
}.freeze
AUDIO_STYLES = {
original: {
format: 'mp3',
content_type: 'audio/mpeg',
convert_options: {
output: {
'loglevel' => 'fatal',
'map_metadata' => '-1',
'q:a' => 2,
},
},
},
}.freeze
VIDEO_CONVERTED_STYLES = { VIDEO_CONVERTED_STYLES = {
small: VIDEO_STYLES[:small], small: VIDEO_STYLES[:small],
original: VIDEO_FORMAT, original: VIDEO_FORMAT,
@ -124,6 +133,9 @@ class MediaAttachment < ApplicationRecord
IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i IMAGE_LIMIT = (ENV['MAX_IMAGE_SIZE'] || 10.megabytes).to_i
VIDEO_LIMIT = (ENV['MAX_VIDEO_SIZE'] || 40.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 :account, inverse_of: :media_attachments, optional: true
belongs_to :status, 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 belongs_to :scheduled_status, inverse_of: :media_attachments, optional: true
@ -156,6 +168,10 @@ class MediaAttachment < ApplicationRecord
remote_url.blank? remote_url.blank?
end end
def not_processed?
processing.present? && !processing_complete?
end
def needs_redownload? def needs_redownload?
file.blank? && remote_url.present? file.blank? && remote_url.present?
end end
@ -203,12 +219,21 @@ class MediaAttachment < ApplicationRecord
"#{x},#{y}" "#{x},#{y}"
end end
attr_writer :delay_processing
def delay_processing?
@delay_processing
end
after_commit :enqueue_processing, on: :create
after_commit :reset_parent_cache, on: :update after_commit :reset_parent_cache, on: :update
before_create :prepare_description, unless: :local? before_create :prepare_description, unless: :local?
before_create :set_shortcode before_create :set_shortcode
before_create :set_processing
before_post_process :set_type_and_extension before_post_process :set_type_and_extension
before_post_process :check_video_dimensions
before_save :set_meta before_save :set_meta
@ -277,6 +302,21 @@ class MediaAttachment < ApplicationRecord
end end
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 def set_meta
meta = populate_meta meta = populate_meta
@ -322,9 +362,11 @@ class MediaAttachment < ApplicationRecord
}.compact }.compact
end end
def reset_parent_cache def enqueue_processing
return if status_id.nil? 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
end end

View File

@ -59,6 +59,14 @@ class Report < ApplicationRecord
end end
def resolve!(acting_account) 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 }] } 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) update!(action_taken: true, action_taken_by_account_id: acting_account.id)
end end

View File

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

View File

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

View File

@ -94,6 +94,7 @@ class ActivityPub::ProcessAccountService < BaseService
@account.statuses_count = outbox_total_items if outbox_total_items.present? @account.statuses_count = outbox_total_items if outbox_total_items.present?
@account.following_count = following_total_items if following_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.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 @account.moved_to_account = @json['movedTo'].present? ? moved_account : nil
end end
@ -166,26 +167,36 @@ class ActivityPub::ProcessAccountService < BaseService
end end
def outbox_total_items def outbox_total_items
collection_total_items('outbox') collection_info('outbox').first
end end
def following_total_items def following_total_items
collection_total_items('following') collection_info('following').first
end end
def followers_total_items def followers_total_items
collection_total_items('followers') collection_info('followers').first
end end
def collection_total_items(type) def following_private?
return if @json[type].blank? !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) return @collections[type] if @collections.key?(type)
collection = fetch_resource_without_id_validation(@json[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 rescue HTTP::Error, OpenSSL::SSL::SSLError
@collections[type] = nil @collections[type] = [nil, nil]
end end
def moved_account def moved_account

View File

@ -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' 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) def call(url)
return if url.blank? return if url.blank?
@ -27,6 +29,7 @@ class FetchResourceService < BaseService
end end
def process_response(response, terminal = false) def process_response(response, terminal = false)
@response_code = response.code
return nil if response.code != 200 return nil if response.code != 200
if ['application/activity+json', 'application/ld+json'].include?(response.mime_type) if ['application/activity+json', 'application/ld+json'].include?(response.mime_type)

View File

@ -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)) @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.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 end
def language_from_option(str) def language_from_option(str)

View File

@ -12,7 +12,7 @@ class ResolveURLService < BaseService
process_local_url process_local_url
elsif !fetched_resource.nil? elsif !fetched_resource.nil?
process_url process_url
elsif @on_behalf_of.present? else
process_url_from_db process_url_from_db
end end
end end
@ -30,6 +30,8 @@ class ResolveURLService < BaseService
end end
def process_url_from_db 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, # 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. # but we can return the toot if we already know about it.
status = Status.find_by(uri: @url) || Status.find_by(url: @url) status = Status.find_by(uri: @url) || Status.find_by(url: @url)
@ -40,7 +42,11 @@ class ResolveURLService < BaseService
end end
def fetched_resource 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 end
def resource_url def resource_url

View File

@ -21,7 +21,7 @@
- unless @warning_presets.empty? - unless @warning_presets.empty?
.fields-group .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 .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) = f.input :text, as: :text, wrapper: :with_block_label, hint: t('simple_form.hints.admin_account_action.text_html', path: admin_warning_presets_path)

View File

@ -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) = 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 %tr
%th= t('admin.accounts.email') %th{ rowspan: can?(:create, :email_domain_block) ? 3 : 2 }= t('admin.accounts.email')
%td= @account.user_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) %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? - if @account.user_unconfirmed_email.present?
%tr %tr
%th= t('admin.accounts.unconfirmed_email') %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) = 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? - 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' = link_to t('admin.domain_blocks.view'), admin_instance_path(@account.domain), class: 'button'
- else - else
= link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive' = link_to t('admin.domain_blocks.add_new'), new_admin_domain_block_path(_domain: @account.domain), class: 'button button--destructive'

View File

@ -3,3 +3,13 @@
%samp= email_domain_block.domain %samp= email_domain_block.domain
%td %td
= table_link_to 'trash', t('admin.email_domain_blocks.delete'), admin_email_domain_block_path(email_domain_block), method: :delete = 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

View File

@ -5,7 +5,10 @@
= render 'shared/error_messages', object: @email_domain_block = render 'shared/error_messages', object: @email_domain_block
.fields-group .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 .actions
= f.button :button, t('.create'), type: :submit = f.button :button, t('.create'), type: :submit

View File

@ -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)

View File

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

View File

@ -5,6 +5,9 @@
= simple_form_for @warning_preset, url: admin_warning_presets_path do |f| = simple_form_for @warning_preset, url: admin_warning_presets_path do |f|
= render 'shared/error_messages', object: @warning_preset = render 'shared/error_messages', object: @warning_preset
.fields-group
= f.input :title, wrapper: :with_block_label
.fields-group .fields-group
= f.input :text, wrapper: :with_block_label = f.input :text, wrapper: :with_block_label
@ -13,18 +16,9 @@
%hr.spacer/ %hr.spacer/
- unless @warning_presets.empty? - if @warning_presets.empty?
.table-wrapper %div.muted-hint.center-text
%table.table = t 'admin.warning_presets.empty'
%thead - else
%tr .announcements-list
%th= t('simple_form.labels.account_warning_preset.text') = render partial: 'warning_preset', collection: @warning_presets
%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') }

View File

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

View File

@ -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

View File

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

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # 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 :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
set :branch, ENV.fetch('BRANCH', 'master') set :branch, ENV.fetch('BRANCH', 'master')

View File

@ -19,4 +19,4 @@ Sidekiq.configure_client do |config|
config.redis = redis_params config.redis = redis_params
end 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)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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