commit
bcfd6e3bb4
|
@ -3,7 +3,7 @@ version: 2
|
|||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6-stretch-node
|
||||
- image: circleci/ruby:2.6-buster-node
|
||||
environment: &ruby_environment
|
||||
BUNDLE_APP_CONFIG: ./.bundle/
|
||||
DB_HOST: localhost
|
||||
|
@ -39,7 +39,6 @@ aliases:
|
|||
steps:
|
||||
- checkout
|
||||
- *attach_workspace
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v1-node-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
@ -49,7 +48,6 @@ aliases:
|
|||
key: v1-node-dependencies-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ./node_modules/
|
||||
|
||||
- *persist_to_workspace
|
||||
|
||||
- &install_system_dependencies
|
||||
|
@ -59,12 +57,16 @@ aliases:
|
|||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
|
||||
## TODO: FIX THESE BUSTER DEPENDANCES
|
||||
sudo wget http://ftp.au.debian.org/debian/pool/main/i/icu/libicu57_57.1-6+deb9u3_amd64.deb
|
||||
sudo dpkg -i libicu57_57.1-6+deb9u3_amd64.deb
|
||||
sudo wget http://ftp.au.debian.org/debian/pool/main/p/protobuf/libprotobuf10_3.0.0-9_amd64.deb
|
||||
sudo dpkg -i libprotobuf10_3.0.0-9_amd64.deb
|
||||
|
||||
- &install_ruby_dependencies
|
||||
steps:
|
||||
- *attach_workspace
|
||||
|
||||
- *install_system_dependencies
|
||||
|
||||
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
|
||||
- *restore_ruby_dependencies
|
||||
- run: bundle install --clean --jobs 16 --path ./vendor/bundle/ --retry 3 --with pam_authentication --without development production && bundle clean
|
||||
|
@ -82,10 +84,8 @@ aliases:
|
|||
- &test_steps
|
||||
steps:
|
||||
- *attach_workspace
|
||||
|
||||
- *install_system_dependencies
|
||||
- run: sudo apt-get install -y ffmpeg
|
||||
|
||||
- run:
|
||||
name: Prepare Tests
|
||||
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare
|
||||
|
@ -105,14 +105,14 @@ jobs:
|
|||
install-ruby2.5:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.5-stretch-node
|
||||
- image: circleci/ruby:2.5-buster-node
|
||||
environment: *ruby_environment
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
install-ruby2.4:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.4-stretch-node
|
||||
- image: circleci/ruby:2.4-buster-node
|
||||
environment: *ruby_environment
|
||||
<<: *install_ruby_dependencies
|
||||
|
||||
|
@ -131,7 +131,7 @@ jobs:
|
|||
test-ruby2.6:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.6-stretch-node
|
||||
- image: circleci/ruby:2.6-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
|
@ -142,7 +142,7 @@ jobs:
|
|||
test-ruby2.5:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.5-stretch-node
|
||||
- image: circleci/ruby:2.5-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
|
@ -153,7 +153,7 @@ jobs:
|
|||
test-ruby2.4:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/ruby:2.4-stretch-node
|
||||
- image: circleci/ruby:2.4-buster-node
|
||||
environment: *ruby_environment
|
||||
- image: circleci/postgres:10.6-alpine
|
||||
environment:
|
||||
|
@ -164,7 +164,7 @@ jobs:
|
|||
test-webui:
|
||||
<<: *defaults
|
||||
docker:
|
||||
- image: circleci/node:12.9-stretch
|
||||
- image: circleci/node:12-buster
|
||||
steps:
|
||||
- *attach_workspace
|
||||
- run: ./bin/retry yarn test:jest
|
||||
|
|
|
@ -4,7 +4,7 @@ FROM ubuntu:18.04 as build-dep
|
|||
SHELL ["bash", "-c"]
|
||||
|
||||
# Install Node v12 (LTS)
|
||||
ENV NODE_VER="12.13.1"
|
||||
ENV NODE_VER="12.14.0"
|
||||
RUN echo "Etc/UTC" > /etc/localtime && \
|
||||
apt update && \
|
||||
apt -y install wget python && \
|
||||
|
|
14
Gemfile
14
Gemfile
|
@ -7,11 +7,11 @@ gem 'pkg-config', '~> 1.4'
|
|||
|
||||
gem 'puma', '~> 4.3'
|
||||
gem 'rails', '~> 5.2.4'
|
||||
gem 'sprockets', '~> 3.7'
|
||||
gem 'sprockets', '~> 3.7.2'
|
||||
gem 'thor', '~> 0.20'
|
||||
|
||||
gem 'hamlit-rails', '~> 0.2'
|
||||
gem 'pg', '~> 1.1'
|
||||
gem 'pg', '~> 1.2'
|
||||
gem 'makara', '~> 0.4'
|
||||
gem 'pghero', '~> 2.4'
|
||||
gem 'dotenv-rails', '~> 2.7'
|
||||
|
@ -31,7 +31,7 @@ gem 'browser'
|
|||
gem 'charlock_holmes', '~> 0.7.7'
|
||||
gem 'iso-639'
|
||||
gem 'chewy', '~> 5.1'
|
||||
gem 'cld3', '~> 3.2.4'
|
||||
gem 'cld3', '~> 3.2.6'
|
||||
gem 'devise', '~> 4.7'
|
||||
gem 'devise-two-factor', '~> 3.1'
|
||||
|
||||
|
@ -50,7 +50,7 @@ gem 'fast_blank', '~> 1.0'
|
|||
gem 'fastimage'
|
||||
gem 'goldfinger', '~> 2.1'
|
||||
gem 'hiredis', '~> 0.6'
|
||||
gem 'redis-namespace', '~> 1.5'
|
||||
gem 'redis-namespace', '~> 1.7'
|
||||
gem 'health_check', git: 'https://github.com/ianheggie/health_check', ref: '0b799ead604f900ed50685e9b2d469cd2befba5b'
|
||||
gem 'html2text'
|
||||
gem 'htmlentities', '~> 4.3'
|
||||
|
@ -61,7 +61,7 @@ gem 'httplog', '~> 1.3'
|
|||
gem 'idn-ruby', require: 'idn'
|
||||
gem 'kaminari', '~> 1.1'
|
||||
gem 'link_header', '~> 0.0'
|
||||
gem 'mime-types', '~> 3.3', require: 'mime/types/columnar'
|
||||
gem 'mime-types', '~> 3.3.1', require: 'mime/types/columnar'
|
||||
gem 'nilsimsa', git: 'https://github.com/witgo/nilsimsa', ref: 'fd184883048b922b176939f851338d0a4971a532'
|
||||
gem 'nokogiri', '~> 1.10'
|
||||
gem 'nsa', '~> 0.2'
|
||||
|
@ -120,8 +120,8 @@ end
|
|||
group :test do
|
||||
gem 'capybara', '~> 3.29'
|
||||
gem 'climate_control', '~> 0.2'
|
||||
gem 'faker', '~> 2.9'
|
||||
gem 'microformats', '~> 4.1'
|
||||
gem 'faker', '~> 2.10'
|
||||
gem 'microformats', '~> 4.2'
|
||||
gem 'rails-controller-testing', '~> 1.0'
|
||||
gem 'rspec-sidekiq', '~> 3.0'
|
||||
gem 'simplecov', '~> 0.17', require: false
|
||||
|
|
46
Gemfile.lock
46
Gemfile.lock
|
@ -185,10 +185,10 @@ GEM
|
|||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
crass (1.0.5)
|
||||
css_parser (1.7.0)
|
||||
css_parser (1.7.1)
|
||||
addressable
|
||||
debug_inspector (0.0.3)
|
||||
derailed_benchmarks (1.4.2)
|
||||
derailed_benchmarks (1.4.3)
|
||||
benchmark-ips (~> 2)
|
||||
get_process_mem (~> 0)
|
||||
heapy (~> 0)
|
||||
|
@ -240,9 +240,9 @@ GEM
|
|||
tzinfo
|
||||
excon (0.71.0)
|
||||
fabrication (2.21.0)
|
||||
faker (2.9.0)
|
||||
faker (2.10.0)
|
||||
i18n (>= 1.6, < 1.8)
|
||||
faraday (0.15.4)
|
||||
faraday (1.0.0)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
fast_blank (1.0.0)
|
||||
fastimage (2.1.7)
|
||||
|
@ -275,8 +275,8 @@ GEM
|
|||
http (~> 3.0)
|
||||
nokogiri (~> 1.8)
|
||||
oj (~> 3.0)
|
||||
hamlit (2.9.3)
|
||||
temple (>= 0.8.0)
|
||||
hamlit (2.11.0)
|
||||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
hamlit-rails (0.2.3)
|
||||
|
@ -374,9 +374,9 @@ GEM
|
|||
microformats (4.1.0)
|
||||
json (~> 2.1)
|
||||
nokogiri (~> 1.8, >= 1.8.3)
|
||||
mime-types (3.3)
|
||||
mime-types (3.3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2019.0904)
|
||||
mime-types-data (3.2019.1009)
|
||||
mimemagic (0.3.3)
|
||||
mini_mime (1.0.2)
|
||||
mini_portile2 (2.4.0)
|
||||
|
@ -434,7 +434,7 @@ GEM
|
|||
pastel (0.7.3)
|
||||
equatable (~> 0.6)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.1.4)
|
||||
pg (1.2.0)
|
||||
pghero (2.4.1)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.4.0)
|
||||
|
@ -454,7 +454,7 @@ GEM
|
|||
pry (~> 0.10)
|
||||
pry-rails (0.3.9)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (4.0.1)
|
||||
public_suffix (4.0.2)
|
||||
puma (4.3.1)
|
||||
nio4r (~> 2.0)
|
||||
pundit (2.1.0)
|
||||
|
@ -463,7 +463,7 @@ GEM
|
|||
rack (2.0.8)
|
||||
rack-attack (6.2.2)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.0)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (2.0.7)
|
||||
rack
|
||||
|
@ -520,7 +520,7 @@ GEM
|
|||
redis-activesupport (5.0.4)
|
||||
activesupport (>= 3, < 6)
|
||||
redis-store (>= 1.3, < 2)
|
||||
redis-namespace (1.6.0)
|
||||
redis-namespace (1.7.0)
|
||||
redis (>= 3.0.4)
|
||||
redis-rack (2.0.4)
|
||||
rack (>= 1.5, < 3)
|
||||
|
@ -532,7 +532,7 @@ GEM
|
|||
redis-store (1.5.0)
|
||||
redis (>= 2.2, < 5)
|
||||
regexp_parser (1.6.0)
|
||||
request_store (1.4.1)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
responders (3.0.0)
|
||||
actionpack (>= 5.0)
|
||||
|
@ -618,21 +618,21 @@ GEM
|
|||
sshkit (1.20.0)
|
||||
net-scp (>= 1.1.2)
|
||||
net-ssh (>= 2.8.0)
|
||||
stackprof (0.2.14)
|
||||
stackprof (0.2.15)
|
||||
statsd-ruby (1.4.0)
|
||||
stoplight (2.2.0)
|
||||
streamio-ffmpeg (3.0.2)
|
||||
multi_json (~> 1.8)
|
||||
strong_migrations (0.5.1)
|
||||
activerecord (>= 5)
|
||||
temple (0.8.1)
|
||||
temple (0.8.2)
|
||||
terminal-table (1.8.0)
|
||||
unicode-display_width (~> 1.1, >= 1.1.1)
|
||||
terrapin (0.6.0)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
thor (0.20.3)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.9)
|
||||
tilt (2.0.10)
|
||||
tty-color (0.5.0)
|
||||
tty-command (0.9.0)
|
||||
pastel (~> 0.7.0)
|
||||
|
@ -654,7 +654,7 @@ GEM
|
|||
tzinfo (>= 1.0.0)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.7.5)
|
||||
unf_ext (0.0.7.6)
|
||||
unicode-display_width (1.6.0)
|
||||
uniform_notifier (1.12.1)
|
||||
warden (1.2.8)
|
||||
|
@ -713,7 +713,7 @@ DEPENDENCIES
|
|||
doorkeeper (~> 5.2)
|
||||
dotenv-rails (~> 2.7)
|
||||
fabrication (~> 2.21)
|
||||
faker (~> 2.9)
|
||||
faker (~> 2.10)
|
||||
fast_blank (~> 1.0)
|
||||
fastimage
|
||||
fog-core (<= 2.1.0)
|
||||
|
@ -759,7 +759,7 @@ DEPENDENCIES
|
|||
parallel (~> 1.19)
|
||||
parallel_tests (~> 2.30)
|
||||
parslet
|
||||
pg (~> 1.1)
|
||||
pg (~> 1.2)
|
||||
pghero (~> 2.4)
|
||||
pkg-config (~> 1.4)
|
||||
posix-spawn!
|
||||
|
@ -778,7 +778,7 @@ DEPENDENCIES
|
|||
rdf-normalize (~> 0.3)
|
||||
redcarpet (~> 3.4)
|
||||
redis (~> 4.1)
|
||||
redis-namespace (~> 1.5)
|
||||
redis-namespace (~> 1.7)
|
||||
redis-rails (~> 5.0)
|
||||
rqrcode (~> 0.10)
|
||||
rspec-rails (~> 3.9)
|
||||
|
@ -808,9 +808,3 @@ DEPENDENCIES
|
|||
webmock (~> 3.7)
|
||||
webpacker (~> 4.2)
|
||||
webpush
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.6.5p114
|
||||
|
||||
BUNDLED WITH
|
||||
1.17.3
|
||||
|
|
|
@ -2,10 +2,6 @@
|
|||
|
||||
module Admin
|
||||
class CustomEmojisController < BaseController
|
||||
include ObfuscateFilename
|
||||
|
||||
obfuscate_filename [:custom_emoji, :image]
|
||||
|
||||
def index
|
||||
authorize :custom_emoji, :index?
|
||||
|
||||
|
|
|
@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
|||
def load_accounts
|
||||
return [] if hide_results?
|
||||
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
|
|
|
@ -21,11 +21,13 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
|||
def load_accounts
|
||||
return [] if hide_results?
|
||||
|
||||
default_accounts.merge(paginated_follows).to_a
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_follows).to_a
|
||||
end
|
||||
|
||||
def hide_results?
|
||||
(@account.user_hides_network? && current_account.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
(@account.user_hides_network? && current_account&.id != @account.id) || (current_account && @account.blocking?(current_account))
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
|
|
|
@ -4,9 +4,6 @@ class Api::V1::MediaController < Api::BaseController
|
|||
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
||||
before_action :require_user!
|
||||
|
||||
include ObfuscateFilename
|
||||
obfuscate_filename :file
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
|
|
|
@ -17,7 +17,9 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
|||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_favourites).to_a
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_favourites).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
|
|
|
@ -17,7 +17,9 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
|||
private
|
||||
|
||||
def load_accounts
|
||||
default_accounts.merge(paginated_statuses).to_a
|
||||
scope = default_accounts
|
||||
scope = scope.where.not(id: current_account.excluded_from_timeline_account_ids) unless current_account.nil?
|
||||
scope.merge(paginated_statuses).to_a
|
||||
end
|
||||
|
||||
def default_accounts
|
||||
|
|
|
@ -25,6 +25,7 @@ class ApplicationController < ActionController::Base
|
|||
rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
|
||||
rescue_from ActionController::UnknownFormat, with: :not_acceptable
|
||||
rescue_from ActionController::ParameterMissing, with: :bad_request
|
||||
rescue_from Paperclip::AdapterRegistry::NoHandlerError, with: :bad_request
|
||||
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
|
||||
|
@ -211,7 +212,12 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def respond_with_error(code)
|
||||
respond_to do |format|
|
||||
format.any do
|
||||
use_pack 'error'
|
||||
render "errors/#{code}", layout: 'error', status: code, formats: [:html]
|
||||
end
|
||||
format.json { render json: { error: Rack::Utils::HTTP_STATUS_CODES[code] }, status: code }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,6 +11,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
before_action :set_cache_headers, only: [:edit, :update]
|
||||
|
||||
skip_before_action :require_functional!, only: [:edit, :update]
|
||||
|
||||
|
@ -114,4 +115,8 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ObfuscateFilename
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
class_methods do
|
||||
def obfuscate_filename(path)
|
||||
before_action do
|
||||
file = params.dig(*path)
|
||||
next if file.nil?
|
||||
|
||||
file.original_filename = SecureRandom.hex(8) + File.extname(file.original_filename)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,10 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class FiltersController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_filters, only: :index
|
||||
before_action :set_filter, only: [:edit, :update, :destroy]
|
||||
before_action :set_pack
|
||||
|
|
|
@ -19,7 +19,6 @@ class FollowerAccountsController < ApplicationController
|
|||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
@relationships = AccountRelationshipsPresenter.new(follows.map(&:account_id), current_user.account_id) if user_signed_in?
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -38,7 +37,11 @@ class FollowerAccountsController < ApplicationController
|
|||
private
|
||||
|
||||
def follows
|
||||
@follows ||= Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
return @follows if defined?(@follows)
|
||||
|
||||
scope = Follow.where(target_account: @account)
|
||||
scope = scope.where.not(account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in?
|
||||
@follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
|
|
|
@ -19,7 +19,6 @@ class FollowingAccountsController < ApplicationController
|
|||
next if @account.user_hides_network?
|
||||
|
||||
follows
|
||||
@relationships = AccountRelationshipsPresenter.new(follows.map(&:target_account_id), current_user.account_id) if user_signed_in?
|
||||
end
|
||||
|
||||
format.json do
|
||||
|
@ -38,7 +37,11 @@ class FollowingAccountsController < ApplicationController
|
|||
private
|
||||
|
||||
def follows
|
||||
@follows ||= Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
return @follows if defined?(@follows)
|
||||
|
||||
scope = Follow.where(account: @account)
|
||||
scope = scope.where.not(target_account_id: current_account.excluded_from_timeline_account_ids) if user_signed_in?
|
||||
@follows = scope.recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
|
||||
end
|
||||
|
||||
def page_requested?
|
||||
|
|
|
@ -6,6 +6,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
before_action :store_current_location
|
||||
before_action :authenticate_resource_owner!
|
||||
before_action :set_pack
|
||||
before_action :set_cache_headers
|
||||
|
||||
include Localized
|
||||
|
||||
|
@ -32,4 +33,8 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
|
|||
def truthy_param?(key)
|
||||
ActiveModel::Type::Boolean.new.cast(params[key])
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
class Settings::BaseController < ApplicationController
|
||||
before_action :set_pack
|
||||
before_action :set_body_classes
|
||||
before_action :set_cache_headers
|
||||
|
||||
private
|
||||
|
||||
|
@ -13,4 +14,8 @@ class Settings::BaseController < ApplicationController
|
|||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
|
||||
def set_cache_headers
|
||||
response.headers['Cache-Control'] = 'no-cache, no-store, max-age=0, must-revalidate'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Settings::ProfilesController < Settings::BaseController
|
||||
include ObfuscateFilename
|
||||
|
||||
layout 'admin'
|
||||
|
||||
before_action :authenticate_user!
|
||||
before_action :set_account
|
||||
|
||||
obfuscate_filename [:account, :avatar]
|
||||
obfuscate_filename [:account, :header]
|
||||
|
||||
def show
|
||||
@account.build_fields
|
||||
end
|
||||
|
|
|
@ -8,12 +8,8 @@ module WellKnown
|
|||
|
||||
def show
|
||||
@webfinger_template = "#{webfinger_url}?resource={uri}"
|
||||
|
||||
respond_to do |format|
|
||||
format.xml { render content_type: 'application/xrd+xml' }
|
||||
end
|
||||
|
||||
expires_in 3.days, public: true
|
||||
render content_type: 'application/xrd+xml', formats: [:xml]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,7 +13,7 @@ module AccountsHelper
|
|||
if account.local?
|
||||
"@#{account.acct}@#{Rails.configuration.x.local_domain}"
|
||||
else
|
||||
"@#{account.acct}"
|
||||
"@#{account.pretty_acct}"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -681,13 +681,13 @@
|
|||
&__links {
|
||||
font-size: 14px;
|
||||
color: $darker-text-color;
|
||||
padding: 10px 0;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
color: $darker-text-color;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
|
||||
strong {
|
||||
|
|
|
@ -4,9 +4,13 @@ import { FormattedNumber } from 'react-intl';
|
|||
export const shortNumberFormat = number => {
|
||||
if (number < 1000) {
|
||||
return <FormattedNumber value={number} />;
|
||||
} else if (number < 1000000) {
|
||||
} else if (number < 10000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
|
||||
} else {
|
||||
} else if (number < 1000000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={0} />K</Fragment>;
|
||||
} else if (number < 10000000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>;
|
||||
} else {
|
||||
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={0} />M</Fragment>;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -28,6 +28,7 @@ export const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL';
|
|||
|
||||
export const STATUS_REVEAL = 'STATUS_REVEAL';
|
||||
export const STATUS_HIDE = 'STATUS_HIDE';
|
||||
export const STATUS_COLLAPSE = 'STATUS_COLLAPSE';
|
||||
|
||||
export const REDRAFT = 'REDRAFT';
|
||||
|
||||
|
@ -320,3 +321,11 @@ export function revealStatus(ids) {
|
|||
ids,
|
||||
};
|
||||
};
|
||||
|
||||
export function toggleStatusCollapse(id, isCollapsed) {
|
||||
return {
|
||||
type: STATUS_COLLAPSE,
|
||||
id,
|
||||
isCollapsed,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -208,10 +208,13 @@ export default class ScrollableList extends PureComponent {
|
|||
}
|
||||
|
||||
attachIntersectionObserver () {
|
||||
this.intersectionObserverWrapper.connect({
|
||||
let nodeOptions = {
|
||||
root: this.node,
|
||||
rootMargin: '300% 0px',
|
||||
});
|
||||
};
|
||||
|
||||
this.intersectionObserverWrapper
|
||||
.connect(this.props.bindToDocument ? {} : nodeOptions);
|
||||
}
|
||||
|
||||
detachIntersectionObserver () {
|
||||
|
|
|
@ -76,6 +76,7 @@ class Status extends ImmutablePureComponent {
|
|||
onEmbed: PropTypes.func,
|
||||
onHeightChange: PropTypes.func,
|
||||
onToggleHidden: PropTypes.func,
|
||||
onToggleCollapsed: PropTypes.func,
|
||||
muted: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
unread: PropTypes.bool,
|
||||
|
@ -102,19 +103,6 @@ class Status extends ImmutablePureComponent {
|
|||
statusId: undefined,
|
||||
};
|
||||
|
||||
// Track height changes we know about to compensate scrolling
|
||||
componentDidMount () {
|
||||
this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
|
||||
}
|
||||
|
||||
getSnapshotBeforeUpdate () {
|
||||
if (this.props.getScrollPosition) {
|
||||
return this.props.getScrollPosition();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.status && nextProps.status.get('id') !== prevState.statusId) {
|
||||
return {
|
||||
|
@ -126,32 +114,6 @@ class Status extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
// Compensate height changes
|
||||
componentDidUpdate (prevProps, prevState, snapshot) {
|
||||
const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card');
|
||||
|
||||
if (doShowCard && !this.didShowCard) {
|
||||
this.didShowCard = true;
|
||||
|
||||
if (snapshot !== null && this.props.updateScrollBottom) {
|
||||
if (this.node && this.node.offsetTop < snapshot.top) {
|
||||
this.props.updateScrollBottom(snapshot.height - snapshot.top);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.node && this.props.getScrollPosition) {
|
||||
const position = this.props.getScrollPosition();
|
||||
if (position !== null && this.node.offsetTop < position.top) {
|
||||
requestAnimationFrame(() => {
|
||||
this.props.updateScrollBottom(position.height - position.top);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleToggleMediaVisibility = () => {
|
||||
this.setState({ showMedia: !this.state.showMedia });
|
||||
}
|
||||
|
@ -196,7 +158,11 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
handleExpandedToggle = () => {
|
||||
this.props.onToggleHidden(this._properStatus());
|
||||
};
|
||||
}
|
||||
|
||||
handleCollapsedToggle = isCollapsed => {
|
||||
this.props.onToggleCollapsed(this._properStatus(), isCollapsed);
|
||||
}
|
||||
|
||||
renderLoadingMediaGallery () {
|
||||
return <div className='media-gallery' style={{ height: '110px' }} />;
|
||||
|
@ -466,7 +432,7 @@ class Status extends ImmutablePureComponent {
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable />
|
||||
<StatusContent status={status} onClick={this.handleClick} expanded={!status.get('hidden')} onExpandedToggle={this.handleExpandedToggle} collapsable onCollapsedToggle={this.handleCollapsedToggle} />
|
||||
|
||||
{media}
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ export default class StatusContent extends React.PureComponent {
|
|||
onExpandedToggle: PropTypes.func,
|
||||
onClick: PropTypes.func,
|
||||
collapsable: PropTypes.bool,
|
||||
onCollapsedToggle: PropTypes.func,
|
||||
};
|
||||
|
||||
state = {
|
||||
hidden: true,
|
||||
collapsed: null, // `collapsed: null` indicates that an element doesn't need collapsing, while `true` or `false` indicates that it does (and is/isn't).
|
||||
};
|
||||
|
||||
_updateStatusLinks () {
|
||||
|
@ -62,14 +62,16 @@ export default class StatusContent extends React.PureComponent {
|
|||
link.setAttribute('rel', 'noopener noreferrer');
|
||||
}
|
||||
|
||||
if (
|
||||
if (this.props.status.get('collapsed', null) === null) {
|
||||
let collapsed =
|
||||
this.props.collapsable
|
||||
&& this.props.onClick
|
||||
&& this.state.collapsed === null
|
||||
&& node.clientHeight > MAX_HEIGHT
|
||||
&& this.props.status.get('spoiler_text').length === 0
|
||||
) {
|
||||
this.setState({ collapsed: true });
|
||||
&& this.props.status.get('spoiler_text').length === 0;
|
||||
|
||||
if(this.props.onCollapsedToggle) this.props.onCollapsedToggle(collapsed);
|
||||
|
||||
this.props.status.set('collapsed', collapsed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -178,6 +180,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
}
|
||||
|
||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||
|
||||
const content = { __html: status.get('contentHtml') };
|
||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||
|
@ -185,7 +188,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
const classNames = classnames('status__content', {
|
||||
'status__content--with-action': this.props.onClick && this.context.router,
|
||||
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
|
||||
'status__content--collapsed': this.state.collapsed === true,
|
||||
'status__content--collapsed': renderReadMore,
|
||||
});
|
||||
|
||||
if (isRtl(status.get('search_index'))) {
|
||||
|
@ -237,7 +240,7 @@ export default class StatusContent extends React.PureComponent {
|
|||
</div>,
|
||||
];
|
||||
|
||||
if (this.state.collapsed) {
|
||||
if (renderReadMore) {
|
||||
output.push(readMoreButton);
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
deleteStatus,
|
||||
hideStatus,
|
||||
revealStatus,
|
||||
toggleStatusCollapse,
|
||||
} from '../actions/statuses';
|
||||
import {
|
||||
unmuteAccount,
|
||||
|
@ -190,6 +191,10 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
}
|
||||
},
|
||||
|
||||
onToggleCollapsed (status, isCollapsed) {
|
||||
dispatch(toggleStatusCollapse(status.get('id'), isCollapsed));
|
||||
},
|
||||
|
||||
onBlockDomain (domain) {
|
||||
dispatch(openModal('CONFIRM', {
|
||||
message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='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.' values={{ domain: <strong>{domain}</strong> }} />,
|
||||
|
|
|
@ -12,6 +12,7 @@ import {
|
|||
STATUS_UNMUTE_SUCCESS,
|
||||
STATUS_REVEAL,
|
||||
STATUS_HIDE,
|
||||
STATUS_COLLAPSE,
|
||||
} from '../actions/statuses';
|
||||
import { TIMELINE_DELETE } from '../actions/timelines';
|
||||
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
|
||||
|
@ -73,6 +74,8 @@ export default function statuses(state = initialState, action) {
|
|||
}
|
||||
});
|
||||
});
|
||||
case STATUS_COLLAPSE:
|
||||
return state.setIn([action.id, 'collapsed'], action.isCollapsed);
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatus(state, action.id, action.references);
|
||||
default:
|
||||
|
|
|
@ -4,9 +4,13 @@ import { FormattedNumber } from 'react-intl';
|
|||
export const shortNumberFormat = number => {
|
||||
if (number < 1000) {
|
||||
return <FormattedNumber value={number} />;
|
||||
} else if (number < 1000000) {
|
||||
} else if (number < 10000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={1} />K</Fragment>;
|
||||
} else {
|
||||
} else if (number < 1000000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000} maximumFractionDigits={0} />K</Fragment>;
|
||||
} else if (number < 10000000) {
|
||||
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={1} />M</Fragment>;
|
||||
} else {
|
||||
return <Fragment><FormattedNumber value={number / 1000000} maximumFractionDigits={0} />M</Fragment>;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -6399,13 +6399,13 @@ noscript {
|
|||
&__links {
|
||||
font-size: 14px;
|
||||
color: $darker-text-color;
|
||||
padding: 10px 0;
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
color: $darker-text-color;
|
||||
text-decoration: none;
|
||||
padding: 10px;
|
||||
padding-top: 20px;
|
||||
padding: 5px 10px;
|
||||
font-weight: 500;
|
||||
|
||||
strong {
|
||||
|
|
|
@ -78,7 +78,7 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
elsif clause[:shortcode]
|
||||
TermClause.new(prefix, operator, ":#{clause[:term]}:")
|
||||
elsif clause[:phrase]
|
||||
PhraseClause.new(prefix, operator, clause[:phrase].map { |p| p[:term].to_s }.join(' '))
|
||||
PhraseClause.new(prefix, operator, clause[:phrase].is_a?(Array) ? clause[:phrase].map { |p| p[:term].to_s }.join(' ') : clause[:phrase].to_s)
|
||||
else
|
||||
raise "Unexpected clause type: #{clause}"
|
||||
end
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
# See: https://jamescrisp.org/2018/05/28/fixing-invalid-query-parameters-invalid-encoding-in-a-rails-app/
|
||||
|
||||
class HandleBadEncodingMiddleware
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
begin
|
||||
Rack::Utils.parse_nested_query(env['QUERY_STRING'].to_s)
|
||||
rescue Rack::Utils::InvalidParameterError
|
||||
env['QUERY_STRING'] = ''
|
||||
end
|
||||
|
||||
@app.call(env)
|
||||
end
|
||||
end
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
class Account < ApplicationRecord
|
||||
USERNAME_RE = /[a-z0-9_]+([a-z0-9_\.-]+[a-z0-9_]+)?/i
|
||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[a-z0-9\.\-]+[a-z0-9]+)?)/i
|
||||
MENTION_RE = /(?<=^|[^\/[:word:]])@((#{USERNAME_RE})(?:@[[:word:]\.\-]+[a-z0-9]+)?)/i
|
||||
|
||||
include AccountAssociations
|
||||
include AccountAvatar
|
||||
|
@ -168,6 +168,10 @@ class Account < ApplicationRecord
|
|||
local? ? username : "#{username}@#{domain}"
|
||||
end
|
||||
|
||||
def pretty_acct
|
||||
local? ? username : "#{username}@#{Addressable::IDNA.to_unicode(domain)}"
|
||||
end
|
||||
|
||||
def local_username_and_domain
|
||||
"#{username}@#{Rails.configuration.x.local_domain}"
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ module Attachmentable
|
|||
GIF_MATRIX_LIMIT = 921_600 # 1280x720px
|
||||
|
||||
included do
|
||||
before_post_process :obfuscate_file_name
|
||||
before_post_process :set_file_extensions
|
||||
before_post_process :check_image_dimensions
|
||||
before_post_process :set_file_content_type
|
||||
|
@ -68,4 +69,14 @@ module Attachmentable
|
|||
rescue Terrapin::CommandLineError
|
||||
''
|
||||
end
|
||||
|
||||
def obfuscate_file_name
|
||||
self.class.attachment_definitions.each_key do |attachment_name|
|
||||
attachment = send(attachment_name)
|
||||
|
||||
next if attachment.blank?
|
||||
|
||||
attachment.instance_write :file_name, SecureRandom.hex(8) + File.extname(attachment.instance_read(:file_name))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -202,9 +202,12 @@ class MediaAttachment < ApplicationRecord
|
|||
end
|
||||
|
||||
after_commit :reset_parent_cache, on: :update
|
||||
|
||||
before_create :prepare_description, unless: :local?
|
||||
before_create :set_shortcode
|
||||
|
||||
before_post_process :set_type_and_extension
|
||||
|
||||
before_save :set_meta
|
||||
|
||||
class << self
|
||||
|
|
|
@ -24,6 +24,10 @@ class REST::AccountSerializer < ActiveModel::Serializer
|
|||
object.id.to_s
|
||||
end
|
||||
|
||||
def acct
|
||||
object.pretty_acct
|
||||
end
|
||||
|
||||
def note
|
||||
Formatter.instance.simplified_format(object)
|
||||
end
|
||||
|
|
|
@ -166,7 +166,7 @@ class BackupService < BaseService
|
|||
io.write(buffer)
|
||||
end
|
||||
end
|
||||
rescue Errno::ENOENT
|
||||
rescue Errno::ENOENT, Seahorse::Client::NetworkingError
|
||||
Rails.logger.warn "Could not backup file #{filename}: file not found"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,15 @@ class ProcessMentionsService < BaseService
|
|||
|
||||
status.text = status.text.gsub(Account::MENTION_RE) do |match|
|
||||
username, domain = Regexp.last_match(1).split('@')
|
||||
|
||||
domain = begin
|
||||
if TagManager.instance.local_domain?(domain)
|
||||
nil
|
||||
else
|
||||
TagManager.instance.normalize_domain(domain)
|
||||
end
|
||||
end
|
||||
|
||||
mentioned_account = Account.find_remote(username, domain)
|
||||
|
||||
if mention_undeliverable?(mentioned_account)
|
||||
|
|
|
@ -12,6 +12,8 @@ class ResolveURLService < BaseService
|
|||
process_local_url
|
||||
elsif !fetched_resource.nil?
|
||||
process_url
|
||||
elsif @on_behalf_of.present?
|
||||
process_url_from_db
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -24,13 +26,17 @@ class ResolveURLService < BaseService
|
|||
status = FetchRemoteStatusService.new.call(resource_url, body)
|
||||
authorize_with @on_behalf_of, status, :show? unless status.nil?
|
||||
status
|
||||
elsif fetched_resource.nil? && @on_behalf_of.present?
|
||||
end
|
||||
end
|
||||
|
||||
def process_url_from_db
|
||||
# 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)
|
||||
authorize_with @on_behalf_of, status, :show? unless status.nil?
|
||||
status
|
||||
end
|
||||
rescue Mastodon::NotPermittedError
|
||||
nil
|
||||
end
|
||||
|
||||
def fetched_resource
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.batch-table__row
|
||||
- if batch_available
|
||||
%label.batch-table__row__select.batch-table__row__select--aligned.batch-checkbox
|
||||
= f.check_box :tag_ids, { multiple: true, include_hidden: false }, tag.id
|
||||
|
||||
|
|
|
@ -47,25 +47,26 @@
|
|||
|
||||
.batch-table.optional
|
||||
.batch-table__toolbar
|
||||
- if params[:pending_review] == '1' || params[:unreviewed] == '1'
|
||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||
= check_box_tag :batch_checkbox_all, nil, false
|
||||
.batch-table__toolbar__actions
|
||||
- if params[:pending_review] == '1'
|
||||
= f.button safe_join([fa_icon('check'), t('admin.accounts.approve')]), name: :approve, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
|
||||
= f.button safe_join([fa_icon('times'), t('admin.accounts.reject')]), name: :reject, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||
- else
|
||||
.batch-table__toolbar__actions
|
||||
%span.neutral-hint= t('generic.no_batch_actions_available')
|
||||
|
||||
.batch-table__body
|
||||
- if @tags.empty?
|
||||
= nothing_here 'nothing-here--under-tabs'
|
||||
- else
|
||||
= render partial: 'tag', collection: @tags, locals: { f: f }
|
||||
= render partial: 'tag', collection: @tags, locals: { f: f, batch_available: params[:pending_review] == '1' || params[:unreviewed] == '1' }
|
||||
|
||||
= paginate @tags
|
||||
|
||||
- if params[:pending_review] == '1'
|
||||
- if params[:pending_review] == '1' || params[:unreviewed] == '1'
|
||||
%hr.spacer/
|
||||
|
||||
%div{ style: 'overflow: hidden' }
|
||||
|
|
|
@ -5,6 +5,10 @@
|
|||
.fields-group
|
||||
= f.input :locale, collection: I18n.available_locales, wrapper: :with_label, include_blank: false, label_method: lambda { |locale| human_locale(locale) }, selected: I18n.locale, hint: false
|
||||
|
||||
- unless I18n.locale == :en
|
||||
.flash-message{ style: "text-align: unset; color: unset" }
|
||||
#{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener", style: "text-decoration: underline")}
|
||||
|
||||
%h4= t 'appearance.advanced_web_interface'
|
||||
|
||||
%p.hint= t 'appearance.advanced_web_interface_hint'
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
- cache @status do
|
||||
.activity-stream.activity-stream--headless
|
||||
.activity-stream.activity-stream--headless
|
||||
= render 'status', status: @status, centered: true, autoplay: @autoplay
|
||||
|
|
|
@ -7,15 +7,18 @@ class RefollowWorker
|
|||
|
||||
def perform(target_account_id)
|
||||
target_account = Account.find(target_account_id)
|
||||
return unless target_account.protocol == :activitypub
|
||||
return unless target_account.activitypub?
|
||||
|
||||
target_account.passive_relationships.where(account: Account.where(domain: nil)).includes(:account).reorder(nil).find_each do |follow|
|
||||
reblogs = follow.show_reblogs?
|
||||
|
||||
target_account.followers.where(domain: nil).reorder(nil).find_each do |follower|
|
||||
# Locally unfollow remote account
|
||||
follower = follow.account
|
||||
follower.unfollow!(target_account)
|
||||
|
||||
# Schedule re-follow
|
||||
begin
|
||||
FollowService.new.call(follower, target_account)
|
||||
FollowService.new.call(follower, target_account, reblogs: reblogs)
|
||||
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound, Mastodon::UnexpectedResponseError, HTTP::Error, OpenSSL::SSL::SSLError
|
||||
next
|
||||
end
|
||||
|
|
|
@ -7,6 +7,7 @@ require 'rails/all'
|
|||
Bundler.require(*Rails.groups)
|
||||
|
||||
require_relative '../app/lib/exceptions'
|
||||
require_relative '../app/middleware/handle_bad_encoding_middleware'
|
||||
require_relative '../lib/paperclip/lazy_thumbnail'
|
||||
require_relative '../lib/paperclip/gif_transcoder'
|
||||
require_relative '../lib/paperclip/video_transcoder'
|
||||
|
@ -118,6 +119,7 @@ module Mastodon
|
|||
|
||||
config.active_job.queue_adapter = :sidekiq
|
||||
|
||||
config.middleware.insert_before Rack::Runtime, HandleBadEncodingMiddleware
|
||||
config.middleware.use Rack::Attack
|
||||
config.middleware.use Rack::Deflater
|
||||
|
||||
|
|
|
@ -8,20 +8,15 @@ Doorkeeper.configure do
|
|||
end
|
||||
|
||||
resource_owner_from_credentials do |_routes|
|
||||
if Devise.ldap_authentication
|
||||
user = User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] })
|
||||
end
|
||||
|
||||
if Devise.pam_authentication
|
||||
user ||= User.authenticate_with_ldap({ :email => request.params[:username], :password => request.params[:password] })
|
||||
end
|
||||
user = User.authenticate_with_ldap(email: request.params[:username], password: request.params[:password]) if Devise.ldap_authentication
|
||||
user ||= User.authenticate_with_pam(email: request.params[:username], password: request.params[:password]) if Devise.pam_authentication
|
||||
|
||||
if user.nil?
|
||||
user = User.find_by(email: request.params[:username])
|
||||
user = nil unless user.valid_password?(request.params[:password])
|
||||
user = nil unless user&.valid_password?(request.params[:password])
|
||||
end
|
||||
|
||||
user if !user&.otp_required_for_login?
|
||||
user unless user&.otp_required_for_login?
|
||||
end
|
||||
|
||||
# If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below.
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
Paperclip::DataUriAdapter.register
|
||||
|
||||
Paperclip.interpolates :filename do |attachment, style|
|
||||
if style == :original
|
||||
attachment.original_filename
|
||||
|
|
|
@ -46,10 +46,7 @@ class Rack::Attack
|
|||
|
||||
PROTECTED_PATHS_REGEX = Regexp.union(PROTECTED_PATHS.map { |path| /\A#{Regexp.escape(path)}/ })
|
||||
|
||||
# Always allow requests from localhost
|
||||
# (blocklist & throttles are skipped)
|
||||
Rack::Attack.safelist('allow from localhost') do |req|
|
||||
# Requests are allowed if the return value is truthy
|
||||
req.remote_ip == '127.0.0.1' || req.remote_ip == '::1'
|
||||
end
|
||||
|
||||
|
|
|
@ -594,6 +594,10 @@ en:
|
|||
animations_and_accessibility: Animations and accessibility
|
||||
confirmation_dialogs: Confirmation dialogs
|
||||
discovery: Discovery
|
||||
localization:
|
||||
body: Mastodon is translated by volunteers.
|
||||
guide_link: https://crowdin.com/project/mastodon
|
||||
guide_link_text: Everyone can contribute.
|
||||
sensitive_content: Sensitive content
|
||||
toot_layout: Toot layout
|
||||
application_mailer:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class FixNullBooleans < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
safety_assured do
|
||||
change_column_default :domain_blocks, :reject_media, false
|
||||
change_column_null :domain_blocks, :reject_media, false, false
|
||||
|
||||
|
@ -14,4 +15,5 @@ class FixNullBooleans < ActiveRecord::Migration[5.1]
|
|||
change_column_default :users, :otp_required_for_login, false
|
||||
change_column_null :users, :otp_required_for_login, false, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
class ChangeAccountsNonnullableInAccountModerationNotes < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
safety_assured do
|
||||
change_column_null :account_moderation_notes, :account_id, false
|
||||
change_column_null :account_moderation_notes, :target_account_id, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
class ChangeAccountIdNonnullableInLists < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
safety_assured do
|
||||
change_column_null :lists, :account_id, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
class ChangeColumnsInNotificationsNonnullable < ActiveRecord::Migration[5.1]
|
||||
def change
|
||||
safety_assured do
|
||||
change_column_null :notifications, :activity_id, false
|
||||
change_column_null :notifications, :activity_type, false
|
||||
change_column_null :notifications, :account_id, false
|
||||
change_column_null :notifications, :from_account_id, false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
16
package.json
16
package.json
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.7.5",
|
||||
"@babel/core": "^7.7.7",
|
||||
"@babel/plugin-proposal-class-properties": "^7.7.4",
|
||||
"@babel/plugin-proposal-decorators": "^7.7.4",
|
||||
"@babel/plugin-transform-react-inline-elements": "^7.7.4",
|
||||
|
@ -69,7 +69,7 @@
|
|||
"@babel/preset-react": "^7.7.4",
|
||||
"@babel/runtime": "^7.7.7",
|
||||
"@gamestdio/websocket": "^0.3.2",
|
||||
"@clusterws/cws": "^0.16.0",
|
||||
"@clusterws/cws": "^0.16.1",
|
||||
"array-includes": "^3.1.1",
|
||||
"atrament": "^0.2.3",
|
||||
"arrow-key-navigation": "^1.1.0",
|
||||
|
@ -107,7 +107,7 @@
|
|||
"intl": "^1.2.5",
|
||||
"intl-messageformat": "^2.2.0",
|
||||
"intl-relativeformat": "^6.4.3",
|
||||
"is-nan": "^1.2.1",
|
||||
"is-nan": "^1.3.0",
|
||||
"js-yaml": "^3.13.1",
|
||||
"lodash": "^4.17.14",
|
||||
"mark-loader": "^0.1.6",
|
||||
|
@ -146,7 +146,7 @@
|
|||
"react-textarea-autosize": "^7.1.2",
|
||||
"react-toggle": "^4.1.1",
|
||||
"redis": "^2.7.1",
|
||||
"redux": "^4.0.4",
|
||||
"redux": "^4.0.5",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"rellax": "^1.10.0",
|
||||
|
@ -157,13 +157,13 @@
|
|||
"sass-loader": "^8.0.0",
|
||||
"stringz": "^2.0.0",
|
||||
"substring-trie": "^1.0.2",
|
||||
"terser-webpack-plugin": "^2.2.2",
|
||||
"terser-webpack-plugin": "^2.3.1",
|
||||
"tesseract.js": "^2.0.0-alpha.16",
|
||||
"throng": "^4.0.0",
|
||||
"tiny-queue": "^0.2.1",
|
||||
"uuid": "^3.3.3",
|
||||
"wavesurfer.js": "^3.2.0",
|
||||
"webpack": "^4.41.2",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-assets-manifest": "^3.1.1",
|
||||
"webpack-bundle-analyzer": "^3.6.0",
|
||||
"webpack-cli": "^3.3.10",
|
||||
|
@ -175,7 +175,7 @@
|
|||
"babel-jest": "^24.9.0",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-import": "~2.19.1",
|
||||
"eslint-plugin-jsx-a11y": "~6.2.3",
|
||||
"eslint-plugin-promise": "~4.2.1",
|
||||
|
@ -183,7 +183,7 @@
|
|||
"jest": "^24.9.0",
|
||||
"raf": "^3.4.1",
|
||||
"react-intl-translations-manager": "^5.0.3",
|
||||
"react-test-renderer": "^16.11.0",
|
||||
"react-test-renderer": "^16.12.0",
|
||||
"sass-lint": "^1.13.1",
|
||||
"webpack-dev-server": "^3.9.0",
|
||||
"yargs": "^15.0.2"
|
||||
|
|
|
@ -85,10 +85,7 @@ describe Api::ProofsController do
|
|||
end
|
||||
|
||||
it 'has the correct avatar url' do
|
||||
first_part = 'https://cb6e6126.ngrok.io/system/accounts/avatars/'
|
||||
last_part = 'original/avatar.gif'
|
||||
|
||||
expect(body_as_json[:avatar]).to match /#{Regexp.quote(first_part)}(?:\d{3,5}\/){3}#{Regexp.quote(last_part)}/
|
||||
expect(body_as_json[:avatar]).to match "https://cb6e6126.ngrok.io#{alice.avatar.url}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,17 +5,36 @@ describe Api::V1::Accounts::FollowerAccountsController do
|
|||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:alice) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate(:follow, target_account: user.account)
|
||||
alice.follow!(account)
|
||||
bob.follow!(account)
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
it 'returns http success' do
|
||||
get :index, params: { account_id: user.account.id, limit: 1 }
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns accounts following the given account' do
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(body_as_json.size).to eq 2
|
||||
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||
end
|
||||
|
||||
it 'does not return blocked users' do
|
||||
user.account.block!(bob)
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(body_as_json.size).to eq 1
|
||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,17 +5,36 @@ describe Api::V1::Accounts::FollowingAccountsController do
|
|||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:accounts') }
|
||||
let(:account) { Fabricate(:account) }
|
||||
let(:alice) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
before do
|
||||
Fabricate(:follow, account: user.account)
|
||||
account.follow!(alice)
|
||||
account.follow!(bob)
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
describe 'GET #index' do
|
||||
it 'returns http success' do
|
||||
get :index, params: { account_id: user.account.id, limit: 1 }
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
end
|
||||
|
||||
it 'returns accounts followed by the given account' do
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(body_as_json.size).to eq 2
|
||||
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||
end
|
||||
|
||||
it 'does not return blocked users' do
|
||||
user.account.block!(bob)
|
||||
get :index, params: { account_id: account.id, limit: 2 }
|
||||
|
||||
expect(body_as_json.size).to eq 1
|
||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
|
|||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
|
||||
let(:alice) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
context 'with an oauth token' do
|
||||
before do
|
||||
|
@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::FavouritedByAccountsController, type: :control
|
|||
let(:status) { Fabricate(:status, account: user.account) }
|
||||
|
||||
before do
|
||||
Fabricate(:favourite, status: status)
|
||||
Favourite.create!(account: alice, status: status)
|
||||
Favourite.create!(account: bob, status: status)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :index, params: { status_id: status.id, limit: 1 }
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.headers['Link'].links.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns accounts who favorited the status' do
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(body_as_json.size).to eq 2
|
||||
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||
end
|
||||
|
||||
it 'does not return blocked users' do
|
||||
user.account.block!(bob)
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(body_as_json.size).to eq 1
|
||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
|
|||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
let(:app) { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
|
||||
let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, application: app, scopes: 'read:accounts') }
|
||||
let(:alice) { Fabricate(:account) }
|
||||
let(:bob) { Fabricate(:account) }
|
||||
|
||||
context 'with an oauth token' do
|
||||
before do
|
||||
|
@ -16,14 +18,28 @@ RSpec.describe Api::V1::Statuses::RebloggedByAccountsController, type: :controll
|
|||
let(:status) { Fabricate(:status, account: user.account) }
|
||||
|
||||
before do
|
||||
Fabricate(:status, reblog_of_id: status.id)
|
||||
Fabricate(:status, account: alice, reblog_of_id: status.id)
|
||||
Fabricate(:status, account: bob, reblog_of_id: status.id)
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :index, params: { status_id: status.id, limit: 1 }
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(response).to have_http_status(200)
|
||||
expect(response.headers['Link'].links.size).to eq(2)
|
||||
end
|
||||
|
||||
it 'returns accounts who reblogged the status' do
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(body_as_json.size).to eq 2
|
||||
expect([body_as_json[0][:id], body_as_json[1][:id]]).to match_array([alice.id.to_s, bob.id.to_s])
|
||||
end
|
||||
|
||||
it 'does not return blocked users' do
|
||||
user.account.block!(bob)
|
||||
get :index, params: { status_id: status.id, limit: 2 }
|
||||
expect(body_as_json.size).to eq 1
|
||||
expect(body_as_json[0][:id]).to eq alice.id.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe ApplicationController, type: :controller do
|
||||
controller do
|
||||
include ObfuscateFilename
|
||||
|
||||
obfuscate_filename :file
|
||||
|
||||
def file
|
||||
render plain: params[:file]&.original_filename
|
||||
end
|
||||
end
|
||||
|
||||
before do
|
||||
routes.draw { get 'file' => 'anonymous#file' }
|
||||
end
|
||||
|
||||
it 'obfusticates filename if the given parameter is specified' do
|
||||
file = fixture_file_upload('files/imports.txt', 'text/plain')
|
||||
post 'file', params: { file: file }
|
||||
expect(response.body).to end_with '.txt'
|
||||
expect(response.body).not_to include 'imports'
|
||||
end
|
||||
|
||||
it 'does nothing if the given parameter is not specified' do
|
||||
post 'file'
|
||||
end
|
||||
end
|
|
@ -22,6 +22,18 @@ describe FollowerAccountsController do
|
|||
expect(assigned[0]).to eq follow1
|
||||
expect(assigned[1]).to eq follow0
|
||||
end
|
||||
|
||||
it 'does not assign blocked users' do
|
||||
user = Fabricate(:user)
|
||||
user.account.block!(follower0)
|
||||
sign_in(user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
assigned = assigns(:follows).to_a
|
||||
expect(assigned.size).to eq 1
|
||||
expect(assigned[0]).to eq follow1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is json' do
|
||||
|
|
|
@ -22,6 +22,18 @@ describe FollowingAccountsController do
|
|||
expect(assigned[0]).to eq follow1
|
||||
expect(assigned[1]).to eq follow0
|
||||
end
|
||||
|
||||
it 'does not assign blocked users' do
|
||||
user = Fabricate(:user)
|
||||
user.account.block!(followee0)
|
||||
sign_in(user)
|
||||
|
||||
expect(response).to have_http_status(200)
|
||||
|
||||
assigned = assigns(:follows).to_a
|
||||
expect(assigned.size).to eq 1
|
||||
expect(assigned[0]).to eq follow1
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is json' do
|
||||
|
|
|
@ -1,47 +1,51 @@
|
|||
require "rails_helper"
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
feature 'Log in' do
|
||||
include ProfileStories
|
||||
|
||||
feature "Log in" do
|
||||
given(:email) { "test@example.com" }
|
||||
given(:password) { "password" }
|
||||
given(:confirmed_at) { Time.zone.now }
|
||||
|
||||
background do
|
||||
Fabricate(:user, email: email, password: password, confirmed_at: confirmed_at)
|
||||
as_a_registered_user
|
||||
visit new_user_session_path
|
||||
end
|
||||
|
||||
subject { page }
|
||||
|
||||
scenario "A valid email and password user is able to log in" do
|
||||
fill_in "user_email", with: email
|
||||
fill_in "user_password", with: password
|
||||
scenario 'A valid email and password user is able to log in' do
|
||||
fill_in 'user_email', with: email
|
||||
fill_in 'user_password', with: password
|
||||
click_on I18n.t('auth.login')
|
||||
|
||||
is_expected.to have_css("div.app-holder")
|
||||
is_expected.to have_css('div.app-holder')
|
||||
end
|
||||
|
||||
scenario "A invalid email and password user is not able to log in" do
|
||||
fill_in "user_email", with: "invalid_email"
|
||||
fill_in "user_password", with: "invalid_password"
|
||||
scenario 'A invalid email and password user is not able to log in' do
|
||||
fill_in 'user_email', with: 'invalid_email'
|
||||
fill_in 'user_password', with: 'invalid_password'
|
||||
click_on I18n.t('auth.login')
|
||||
|
||||
is_expected.to have_css(".flash-message", text: failure_message("invalid"))
|
||||
is_expected.to have_css('.flash-message', text: failure_message('invalid'))
|
||||
end
|
||||
|
||||
context do
|
||||
given(:confirmed_at) { nil }
|
||||
|
||||
scenario "A unconfirmed user is able to log in" do
|
||||
fill_in "user_email", with: email
|
||||
fill_in "user_password", with: password
|
||||
scenario 'A unconfirmed user is able to log in' do
|
||||
fill_in 'user_email', with: email
|
||||
fill_in 'user_password', with: password
|
||||
click_on I18n.t('auth.login')
|
||||
|
||||
is_expected.to have_css("div.admin-wrapper")
|
||||
is_expected.to have_css('div.admin-wrapper')
|
||||
end
|
||||
end
|
||||
|
||||
def failure_message(message)
|
||||
keys = User.authentication_keys.map { |key| User.human_attribute_name(key) }
|
||||
I18n.t("devise.failure.#{message}", authentication_keys: keys.join("support.array.words_connector"))
|
||||
I18n.t("devise.failure.#{message}", authentication_keys: keys.join('support.array.words_connector'))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
feature 'Profile' do
|
||||
include ProfileStories
|
||||
|
||||
given(:local_domain) { ENV['LOCAL_DOMAIN'] }
|
||||
|
||||
background do
|
||||
as_a_logged_in_user
|
||||
with_alice_as_local_user
|
||||
end
|
||||
|
||||
subject { page }
|
||||
|
||||
scenario 'I can view Annes public account' do
|
||||
visit account_path('alice')
|
||||
|
||||
is_expected.to have_title("alice (@alice@#{local_domain})")
|
||||
|
||||
within('.public-account-header h1') do
|
||||
is_expected.to have_content("alice @alice@#{local_domain}")
|
||||
end
|
||||
|
||||
bio_elem = first('.public-account-bio')
|
||||
expect(bio_elem).to have_content(alice_bio)
|
||||
# The bio has hashtags made clickable
|
||||
expect(bio_elem).to have_link('cryptology')
|
||||
expect(bio_elem).to have_link('science')
|
||||
# Nicknames are make clickable
|
||||
expect(bio_elem).to have_link('@alice')
|
||||
expect(bio_elem).to have_link('@bob')
|
||||
# Nicknames not on server are not clickable
|
||||
expect(bio_elem).not_to have_link('@pepe')
|
||||
end
|
||||
|
||||
scenario 'I can change my account' do
|
||||
visit settings_profile_path
|
||||
fill_in 'Display name', with: 'Bob'
|
||||
fill_in 'Bio', with: 'Bob is silent'
|
||||
click_on 'Save changes'
|
||||
is_expected.to have_content 'Changes successfully saved!'
|
||||
|
||||
# View my own public profile and see the changes
|
||||
click_link "Bob @bob@#{local_domain}"
|
||||
|
||||
within('.public-account-header h1') do
|
||||
is_expected.to have_content("Bob @bob@#{local_domain}")
|
||||
end
|
||||
expect(first('.public-account-bio')).to have_content('Bob is silent')
|
||||
end
|
||||
end
|
|
@ -0,0 +1,21 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe HandleBadEncodingMiddleware do
|
||||
let(:app) { double() }
|
||||
let(:middleware) { HandleBadEncodingMiddleware.new(app) }
|
||||
|
||||
it "request with query string is unchanged" do
|
||||
expect(app).to receive(:call).with("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
|
||||
middleware.call("PATH" => "/some/path", "QUERY_STRING" => "name=fred")
|
||||
end
|
||||
|
||||
it "request with no query string is unchanged" do
|
||||
expect(app).to receive(:call).with("PATH" => "/some/path")
|
||||
middleware.call("PATH" => "/some/path")
|
||||
end
|
||||
|
||||
it "request with invalid encoding in query string drops query string" do
|
||||
expect(app).to receive(:call).with("QUERY_STRING" => "", "PATH" => "/some/path")
|
||||
middleware.call("QUERY_STRING" => "q=%2Fsearch%2Fall%Forder%3Ddescending%26page%3D5%26sort%3Dcreated_at", "PATH" => "/some/path")
|
||||
end
|
||||
end
|
|
@ -823,4 +823,5 @@ RSpec.describe Account, type: :model do
|
|||
end
|
||||
|
||||
include_examples 'AccountAvatar', :account
|
||||
include_examples 'AccountHeader', :account
|
||||
end
|
||||
|
|
|
@ -133,6 +133,24 @@ RSpec.describe MediaAttachment, type: :model do
|
|||
expect(media.file.meta["small"]["height"]).to eq 327
|
||||
expect(media.file.meta["small"]["aspect"]).to eq 490.0 / 327
|
||||
end
|
||||
|
||||
it 'gives the file a random name' do
|
||||
expect(media.file_file_name).to_not eq 'attachment.jpg'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'base64-encoded jpeg' do
|
||||
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
|
||||
let(:media) { MediaAttachment.create(account: Fabricate(:account), file: base64_attachment) }
|
||||
|
||||
it 'saves media attachment' do
|
||||
expect(media.persisted?).to be true
|
||||
expect(media.file).to_not be_nil
|
||||
end
|
||||
|
||||
it 'gives the file a file name' do
|
||||
expect(media.file_file_name).to_not be_blank
|
||||
end
|
||||
end
|
||||
|
||||
describe 'descriptions for remote attachments' do
|
||||
|
|
|
@ -5,11 +5,11 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
let(:visibility) { :public }
|
||||
let(:status) { Fabricate(:status, account: account, text: "Hello @#{remote_user.acct}", visibility: visibility) }
|
||||
|
||||
subject { ProcessMentionsService.new }
|
||||
|
||||
context 'OStatus with public toot' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||
|
||||
subject { ProcessMentionsService.new }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.salmon_url)
|
||||
subject.call(status)
|
||||
|
@ -24,8 +24,6 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
let(:visibility) { :private }
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :ostatus, domain: 'example.com', salmon_url: 'http://salmon.example.com') }
|
||||
|
||||
subject { ProcessMentionsService.new }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.salmon_url)
|
||||
subject.call(status)
|
||||
|
@ -41,10 +39,9 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
end
|
||||
|
||||
context 'ActivityPub' do
|
||||
context do
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox') }
|
||||
|
||||
subject { ProcessMentionsService.new }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
|
@ -59,11 +56,28 @@ RSpec.describe ProcessMentionsService, type: :service do
|
|||
end
|
||||
end
|
||||
|
||||
context 'with an IDN domain' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'sneak', protocol: :activitypub, domain: 'xn--hresiar-mxa.ch', inbox_url: 'http://example.com/inbox') }
|
||||
let(:status) { Fabricate(:status, account: account, text: "Hello @sneak@hæresiar.ch") }
|
||||
|
||||
before do
|
||||
stub_request(:post, remote_user.inbox_url)
|
||||
subject.call(status)
|
||||
end
|
||||
|
||||
it 'creates a mention' do
|
||||
expect(remote_user.mentions.where(status: status).count).to eq 1
|
||||
end
|
||||
|
||||
it 'sends activity to the inbox' do
|
||||
expect(a_request(:post, remote_user.inbox_url)).to have_been_made.once
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'Temporarily-unreachable ActivityPub user' do
|
||||
let(:remote_user) { Fabricate(:account, username: 'remote_user', protocol: :activitypub, domain: 'example.com', inbox_url: 'http://example.com/inbox', last_webfingered_at: nil) }
|
||||
|
||||
subject { ProcessMentionsService.new }
|
||||
|
||||
before do
|
||||
stub_request(:get, "https://example.com/.well-known/host-meta").to_return(status: 404)
|
||||
stub_request(:get, "https://example.com/.well-known/webfinger?resource=acct:remote_user@example.com").to_return(status: 500)
|
||||
|
|
|
@ -16,4 +16,24 @@ shared_examples 'AccountAvatar' do |fabricator|
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'base64-encoded files' do
|
||||
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
|
||||
let(:account) { Fabricate(fabricator, avatar: base64_attachment) }
|
||||
|
||||
it 'saves avatar' do
|
||||
expect(account.persisted?).to be true
|
||||
expect(account.avatar).to_not be_nil
|
||||
end
|
||||
|
||||
it 'gives the avatar a file name' do
|
||||
expect(account.avatar_file_name).to_not be_blank
|
||||
end
|
||||
|
||||
it 'saves a new avatar under a different file name' do
|
||||
previous_file_name = account.avatar_file_name
|
||||
account.update(avatar: base64_attachment)
|
||||
expect(account.avatar_file_name).to_not eq previous_file_name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
shared_examples 'AccountHeader' do |fabricator|
|
||||
describe 'base64-encoded files' do
|
||||
let(:base64_attachment) { "data:image/jpeg;base64,#{Base64.encode64(attachment_fixture('attachment.jpg').read)}" }
|
||||
let(:account) { Fabricate(fabricator, header: base64_attachment) }
|
||||
|
||||
it 'saves header' do
|
||||
expect(account.persisted?).to be true
|
||||
expect(account.header).to_not be_nil
|
||||
end
|
||||
|
||||
it 'gives the header a file name' do
|
||||
expect(account.header_file_name).to_not be_blank
|
||||
end
|
||||
|
||||
it 'saves a new header under a different file name' do
|
||||
previous_file_name = account.header_file_name
|
||||
account.update(header: base64_attachment)
|
||||
expect(account.header_file_name).to_not eq previous_file_name
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ProfileStories
|
||||
attr_reader :bob, :alice, :alice_bio
|
||||
|
||||
def as_a_registered_user
|
||||
@bob = Fabricate(
|
||||
:user,
|
||||
email: email, password: password, confirmed_at: confirmed_at,
|
||||
account: Fabricate(:account, username: 'bob')
|
||||
)
|
||||
end
|
||||
|
||||
def as_a_logged_in_user
|
||||
as_a_registered_user
|
||||
visit new_user_session_path
|
||||
fill_in 'user_email', with: email
|
||||
fill_in 'user_password', with: password
|
||||
click_on I18n.t('auth.login')
|
||||
end
|
||||
|
||||
def with_alice_as_local_user
|
||||
@alice_bio = '@alice and @bob are fictional characters commonly used as'\
|
||||
'placeholder names in #cryptology, as well as #science and'\
|
||||
'engineering 📖 literature. Not affilated with @pepe.'
|
||||
|
||||
@alice = Fabricate(
|
||||
:user,
|
||||
email: 'alice@example.com', password: password, confirmed_at: confirmed_at,
|
||||
account: Fabricate(:account, username: 'alice', note: @alice_bio)
|
||||
)
|
||||
end
|
||||
|
||||
def confirmed_at
|
||||
@confirmed_at ||= Time.zone.now
|
||||
end
|
||||
|
||||
def email
|
||||
@email ||= 'test@example.com'
|
||||
end
|
||||
|
||||
def password
|
||||
@password ||= 'password'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
describe RefollowWorker do
|
||||
subject { described_class.new }
|
||||
let(:account) { Fabricate(:account, domain: 'example.org', protocol: :activitypub) }
|
||||
let(:alice) { Fabricate(:account, domain: nil, username: 'alice') }
|
||||
let(:bob) { Fabricate(:account, domain: nil, username: 'bob') }
|
||||
|
||||
describe 'perform' do
|
||||
let(:service) { double }
|
||||
|
||||
before do
|
||||
allow(FollowService).to receive(:new).and_return(service)
|
||||
allow(service).to receive(:call)
|
||||
|
||||
alice.follow!(account, reblogs: true)
|
||||
bob.follow!(account, reblogs: false)
|
||||
end
|
||||
|
||||
it 'calls FollowService for local followers' do
|
||||
result = subject.perform(account.id)
|
||||
|
||||
expect(result).to be_nil
|
||||
expect(service).to have_received(:call).with(alice, account, reblogs: true)
|
||||
expect(service).to have_received(:call).with(bob, account, reblogs: false)
|
||||
end
|
||||
end
|
||||
end
|
146
yarn.lock
146
yarn.lock
|
@ -9,15 +9,15 @@
|
|||
dependencies:
|
||||
"@babel/highlight" "^7.0.0"
|
||||
|
||||
"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.5":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.5.tgz#ae1323cd035b5160293307f50647e83f8ba62f7e"
|
||||
integrity sha512-M42+ScN4+1S9iB6f+TL7QBpoQETxbclx+KNoKJABghnKYE+fMzSGqst0BZJc8CpI625bwPwYgUyRvxZ+0mZzpw==
|
||||
"@babel/core@^7.1.0", "@babel/core@^7.4.5", "@babel/core@^7.7.7":
|
||||
version "7.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.7.7.tgz#ee155d2e12300bcc0cff6a8ad46f2af5063803e9"
|
||||
integrity sha512-jlSjuj/7z138NLZALxVgrx13AOtqip42ATZP7+kYl53GvDV6+4dCek1mVUo8z8c8Xnw/mx2q3d9HWh3griuesQ==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.5.5"
|
||||
"@babel/generator" "^7.7.4"
|
||||
"@babel/generator" "^7.7.7"
|
||||
"@babel/helpers" "^7.7.4"
|
||||
"@babel/parser" "^7.7.5"
|
||||
"@babel/parser" "^7.7.7"
|
||||
"@babel/template" "^7.7.4"
|
||||
"@babel/traverse" "^7.7.4"
|
||||
"@babel/types" "^7.7.4"
|
||||
|
@ -29,10 +29,10 @@
|
|||
semver "^5.4.1"
|
||||
source-map "^0.5.0"
|
||||
|
||||
"@babel/generator@^7.0.0", "@babel/generator@^7.7.4":
|
||||
version "7.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.4.tgz#db651e2840ca9aa66f327dcec1dc5f5fa9611369"
|
||||
integrity sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==
|
||||
"@babel/generator@^7.0.0", "@babel/generator@^7.7.4", "@babel/generator@^7.7.7":
|
||||
version "7.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.7.7.tgz#859ac733c44c74148e1a72980a64ec84b85f4f45"
|
||||
integrity sha512-/AOIBpHh/JU1l0ZFS4kiRCBnLi6OTHzh0RPk3h9isBxkkqELtQNFi1Vr/tiG9p1yfoUdKVwISuXWQR+hwwM4VQ==
|
||||
dependencies:
|
||||
"@babel/types" "^7.7.4"
|
||||
jsesc "^2.5.1"
|
||||
|
@ -252,10 +252,10 @@
|
|||
esutils "^2.0.2"
|
||||
js-tokens "^4.0.0"
|
||||
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.5":
|
||||
version "7.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.5.tgz#cbf45321619ac12d83363fcf9c94bb67fa646d71"
|
||||
integrity sha512-KNlOe9+/nk4i29g0VXgl8PEXIRms5xKLJeuZ6UptN0fHv+jDiriG+y94X6qAgWTR0h3KaoM1wK5G5h7MHFRSig==
|
||||
"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.7.4", "@babel/parser@^7.7.7":
|
||||
version "7.7.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.7.7.tgz#1b886595419cf92d811316d5b715a53ff38b4937"
|
||||
integrity sha512-WtTZMZAZLbeymhkd/sEaPD8IQyGAhmuTuvTzLiCFM7iXiVdY0gc0IaI+cW0fh1BnSMbJSzXX6/fHllgHKwHhXw==
|
||||
|
||||
"@babel/plugin-proposal-async-generator-functions@^7.7.4":
|
||||
version "7.7.4"
|
||||
|
@ -795,10 +795,10 @@
|
|||
lodash "^4.17.13"
|
||||
to-fast-properties "^2.0.0"
|
||||
|
||||
"@clusterws/cws@^0.16.0":
|
||||
version "0.16.0"
|
||||
resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.0.tgz#f6116cbf3a8b7ad0657916616ce5f8248746b797"
|
||||
integrity sha512-YeGpAPIdkBsOnAkmFKVMWEjCKDH900U2if0B+nc1imfv+64AIb2JX2xiTA6BLDLppEgWV5c6bpWESjbHCNblHw==
|
||||
"@clusterws/cws@^0.16.1":
|
||||
version "0.16.1"
|
||||
resolved "https://registry.yarnpkg.com/@clusterws/cws/-/cws-0.16.1.tgz#443c47909601e87b881ffb2c4c83cbf27a228487"
|
||||
integrity sha512-OamMsXwVppfmwX14Ed1msJJN0KNi+VBQh5AkAqUvIGTcJyHcjpsKjU15wS8QumvEewxx9gvGp7aSDop0/R5Gqg==
|
||||
|
||||
"@cnakazawa/watch@^1.0.3":
|
||||
version "1.0.3"
|
||||
|
@ -3247,7 +3247,7 @@ default-gateway@^4.2.0:
|
|||
execa "^1.0.0"
|
||||
ip-regex "^2.1.0"
|
||||
|
||||
define-properties@^1.1.1, define-properties@^1.1.2, define-properties@^1.1.3:
|
||||
define-properties@^1.1.2, define-properties@^1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
|
||||
integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
|
||||
|
@ -3942,10 +3942,10 @@ eslint@^2.7.0:
|
|||
text-table "~0.2.0"
|
||||
user-home "^2.0.0"
|
||||
|
||||
eslint@^6.7.2:
|
||||
version "6.7.2"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.7.2.tgz#c17707ca4ad7b2d8af986a33feba71e18a9fecd1"
|
||||
integrity sha512-qMlSWJaCSxDFr8fBPvJM9kJwbazrhNcBU3+DszDW1OlEwKBBRWsJc7NJFelvwQpanHCR14cOLD41x8Eqvo3Nng==
|
||||
eslint@^6.8.0:
|
||||
version "6.8.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb"
|
||||
integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
ajv "^6.10.0"
|
||||
|
@ -4031,12 +4031,7 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
|
|||
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
|
||||
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
|
||||
|
||||
esutils@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
|
||||
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
|
||||
|
||||
esutils@^2.0.2:
|
||||
esutils@^2.0.0, esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
@ -4385,10 +4380,10 @@ find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
|
|||
make-dir "^2.0.0"
|
||||
pkg-dir "^3.0.0"
|
||||
|
||||
find-cache-dir@^3.0.0, find-cache-dir@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.1.0.tgz#9935894999debef4cf9f677fdf646d002c4cdecb"
|
||||
integrity sha512-zw+EFiNBNPgI2NTrKkDd1xd7q0cs6wr/iWnr/oUkI0yF9K9GqQ+riIt4aiyFaaqpaWbxPrJXHI+QvmNUQbX+0Q==
|
||||
find-cache-dir@^3.0.0, find-cache-dir@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874"
|
||||
integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg==
|
||||
dependencies:
|
||||
commondir "^1.0.1"
|
||||
make-dir "^3.0.0"
|
||||
|
@ -5620,12 +5615,12 @@ is-my-json-valid@^2.10.0:
|
|||
jsonpointer "^4.0.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
is-nan@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2"
|
||||
integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI=
|
||||
is-nan@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.3.0.tgz#85d1f5482f7051c2019f5673ccebdb06f3b0db03"
|
||||
integrity sha512-z7bbREymOqt2CCaZVly8aC4ML3Xhfi0ekuOnjO2L8vKdl+CttdVoGZQhd4adMFAsxQ5VeRVwORs4tU8RH+HFtQ==
|
||||
dependencies:
|
||||
define-properties "^1.1.1"
|
||||
define-properties "^1.1.3"
|
||||
|
||||
is-number-object@^1.0.4:
|
||||
version "1.0.4"
|
||||
|
@ -8785,15 +8780,15 @@ react-swipeable-views@^0.13.3:
|
|||
react-swipeable-views-utils "^0.13.3"
|
||||
warning "^4.0.1"
|
||||
|
||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.11.0:
|
||||
version "16.11.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.11.0.tgz#72574566496462c808ac449b0287a4c0a1a7d8f8"
|
||||
integrity sha512-nh9gDl8R4ut+ZNNb2EeKO5VMvTKxwzurbSMuGBoKtjpjbg8JK/u3eVPVNi1h1Ue+eYK9oSzJjb+K3lzLxyA4ag==
|
||||
react-test-renderer@^16.0.0-0, react-test-renderer@^16.12.0:
|
||||
version "16.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.12.0.tgz#11417ffda579306d4e841a794d32140f3da1b43f"
|
||||
integrity sha512-Vj/teSqt2oayaWxkbhQ6gKis+t5JrknXfPVo+aIJ8QwYAqMPH77uptOdrlphyxl8eQI/rtkOYg86i/UWkpFu0w==
|
||||
dependencies:
|
||||
object-assign "^4.1.1"
|
||||
prop-types "^15.6.2"
|
||||
react-is "^16.8.6"
|
||||
scheduler "^0.17.0"
|
||||
scheduler "^0.18.0"
|
||||
|
||||
react-textarea-autosize@^7.1.2:
|
||||
version "7.1.2"
|
||||
|
@ -8946,10 +8941,10 @@ redux-thunk@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.3.0.tgz#51c2c19a185ed5187aaa9a2d08b666d0d6467622"
|
||||
integrity sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==
|
||||
|
||||
redux@^4.0.4:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.4.tgz#4ee1aeb164b63d6a1bcc57ae4aa0b6e6fa7a3796"
|
||||
integrity sha512-vKv4WdiJxOWKxK0yRoaK3Y4pxxB0ilzVx6dszU2W8wLxlb2yikRph4iV/ymtdJ6ZxpBLFbyrxklnT5yBbQSl3Q==
|
||||
redux@^4.0.5:
|
||||
version "4.0.5"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.5.tgz#4db5de5816e17891de8a80c424232d06f051d93f"
|
||||
integrity sha512-VSz1uMAH24DM6MF72vcojpYPtrTUu3ByVWfPL1nPfVRb5mZVTve5GnNCUV53QM/BZ66xfWrm0CTWoM+Xlz8V1w==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
@ -9395,14 +9390,6 @@ sax@^1.2.4, sax@~1.2.4:
|
|||
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
|
||||
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
|
||||
|
||||
scheduler@^0.17.0:
|
||||
version "0.17.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.17.0.tgz#7c9c673e4ec781fac853927916d1c426b6f3ddfe"
|
||||
integrity sha512-7rro8Io3tnCPuY4la/NuI5F2yfESpnfZyT6TtkXnSWVkcu0BCDJ+8gk5ozUaFaxpIyNuWAPXrH0yFcSi28fnDA==
|
||||
dependencies:
|
||||
loose-envify "^1.1.0"
|
||||
object-assign "^4.1.1"
|
||||
|
||||
scheduler@^0.18.0:
|
||||
version "0.18.0"
|
||||
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.18.0.tgz#5901ad6659bc1d8f3fdaf36eb7a67b0d6746b1c4"
|
||||
|
@ -9487,12 +9474,7 @@ send@0.17.1:
|
|||
range-parser "~1.2.1"
|
||||
statuses "~1.5.0"
|
||||
|
||||
serialize-javascript@^1.7.0:
|
||||
version "1.9.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
|
||||
integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
|
||||
|
||||
serialize-javascript@^2.1.1, serialize-javascript@^2.1.2:
|
||||
serialize-javascript@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61"
|
||||
integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==
|
||||
|
@ -10164,39 +10146,39 @@ tcomb@^2.5.0:
|
|||
resolved "https://registry.yarnpkg.com/tcomb/-/tcomb-2.7.0.tgz#10d62958041669a5d53567b9a4ee8cde22b1c2b0"
|
||||
integrity sha1-ENYpWAQWaaXVNWe5pO6M3iKxwrA=
|
||||
|
||||
terser-webpack-plugin@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4"
|
||||
integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg==
|
||||
terser-webpack-plugin@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz#5ecaf2dbdc5fb99745fd06791f46fc9ddb1c9a7c"
|
||||
integrity sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==
|
||||
dependencies:
|
||||
cacache "^12.0.2"
|
||||
find-cache-dir "^2.1.0"
|
||||
is-wsl "^1.1.0"
|
||||
schema-utils "^1.0.0"
|
||||
serialize-javascript "^1.7.0"
|
||||
serialize-javascript "^2.1.2"
|
||||
source-map "^0.6.1"
|
||||
terser "^4.1.2"
|
||||
webpack-sources "^1.4.0"
|
||||
worker-farm "^1.7.0"
|
||||
|
||||
terser-webpack-plugin@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.2.2.tgz#2a6e00237125564a455ad69b22e08ee59420473a"
|
||||
integrity sha512-/CHMNswPMAwuD2kd++qys8UmBRmsshPSzHw4BlDwurPtK9YjeK93OV89YWkJulHk972cs07K/7Z92V6PNjWF8A==
|
||||
terser-webpack-plugin@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.1.tgz#6a63c27debc15b25ffd2588562ee2eeabdcab923"
|
||||
integrity sha512-dNxivOXmDgZqrGxOttBH6B4xaxT4zNC+Xd+2K8jwGDMK5q2CZI+KZMA1AAnSRT+BTRvuzKsDx+fpxzPAmAMVcA==
|
||||
dependencies:
|
||||
cacache "^13.0.1"
|
||||
find-cache-dir "^3.1.0"
|
||||
find-cache-dir "^3.2.0"
|
||||
jest-worker "^24.9.0"
|
||||
schema-utils "^2.6.1"
|
||||
serialize-javascript "^2.1.1"
|
||||
serialize-javascript "^2.1.2"
|
||||
source-map "^0.6.1"
|
||||
terser "^4.4.2"
|
||||
terser "^4.4.3"
|
||||
webpack-sources "^1.4.3"
|
||||
|
||||
terser@^4.1.2, terser@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.2.tgz#448fffad0245f4c8a277ce89788b458bfd7706e8"
|
||||
integrity sha512-Uufrsvhj9O1ikwgITGsZ5EZS6qPokUOkCegS7fYOdGTv+OA90vndUbU6PEjr5ePqHfNUbGyMO7xyIZv2MhsALQ==
|
||||
terser@^4.1.2, terser@^4.4.3:
|
||||
version "4.4.3"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-4.4.3.tgz#401abc52b88869cf904412503b1eb7da093ae2f0"
|
||||
integrity sha512-0ikKraVtRDKGzHrzkCv5rUNDzqlhmhowOBqC0XqUHFpW+vJ45+20/IFBcebwKfiS2Z9fJin6Eo+F1zLZsxi8RA==
|
||||
dependencies:
|
||||
commander "^2.20.0"
|
||||
source-map "~0.6.1"
|
||||
|
@ -10858,10 +10840,10 @@ webpack-sources@^1.0.0, webpack-sources@^1.0.1, webpack-sources@^1.1.0, webpack-
|
|||
source-list-map "^2.0.0"
|
||||
source-map "~0.6.1"
|
||||
|
||||
webpack@^4.41.2:
|
||||
version "4.41.2"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.2.tgz#c34ec76daa3a8468c9b61a50336d8e3303dce74e"
|
||||
integrity sha512-Zhw69edTGfbz9/8JJoyRQ/pq8FYUoY0diOXqW0T6yhgdhCv6wr0hra5DwwWexNRns2Z2+gsnrNcbe9hbGBgk/A==
|
||||
webpack@^4.41.5:
|
||||
version "4.41.5"
|
||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.5.tgz#3210f1886bce5310e62bb97204d18c263341b77c"
|
||||
integrity sha512-wp0Co4vpyumnp3KlkmpM5LWuzvZYayDwM2n17EHFr4qxBBbRokC7DJawPJC7TfSFZ9HZ6GsdH40EBj4UV0nmpw==
|
||||
dependencies:
|
||||
"@webassemblyjs/ast" "1.8.5"
|
||||
"@webassemblyjs/helper-module-context" "1.8.5"
|
||||
|
@ -10883,7 +10865,7 @@ webpack@^4.41.2:
|
|||
node-libs-browser "^2.2.1"
|
||||
schema-utils "^1.0.0"
|
||||
tapable "^1.1.3"
|
||||
terser-webpack-plugin "^1.4.1"
|
||||
terser-webpack-plugin "^1.4.3"
|
||||
watchpack "^1.6.0"
|
||||
webpack-sources "^1.4.1"
|
||||
|
||||
|
|
Loading…
Reference in New Issue