Merge branch 'master' into glitch-soc/merge-upstream

Conflicts:
- `app/controllers/statuses_controller.rb`:
  Upstream disabled the embed controller for reblogs.
  Not a real conflict, but glitch-soc has an extra line to deal
  with its theming system.
  Ported upstream changes.
- `app/javascript/packs/public.js`:
  Upstream made changes to get rid of most inline CSS, this changes
  javascript for public pages, which in glitch are split between
  different files. Ported those changes.
- `app/models/status.rb`:
  Upstream changed the block check in `Status#permitted_for` to
  include domain-block checks. Not a real conflict with glitch-soc,
  but our scope is slightly different, as our scope for
  unauthenticated access do not include instance-local toots.
  Ported upstream changes.
- `app/serializers/rest/instance_serializer.rb`:
  Not a real conflict, upstream added a new field to the instance
  serializer, the conflict is one line above since we added more of
  that.
  Ported upstream changes.
- `app/views/settings/profiles/show.html.haml`:
  Upstream got rid of most inline CSS and moved hidden elements
  to data attributes in the process, in fields were we have
  different values.
  Ported upstream changes while keeping our glitch-specific
  values.
- `app/views/statuses/_simple_status.html.haml`:
  Upstream got rid of inline CSS on an HAML line we treat
  differently, stripping empty text nodes.
  Ported upstream changes to the style attribute, keeping
  the empty text node stripping behavior.
This commit is contained in:
Thibaut Girka 2020-05-03 21:20:42 +02:00
commit a22e6a3683
81 changed files with 2175 additions and 629 deletions

View File

@ -5,12 +5,13 @@ aliases:
docker: docker:
- image: circleci/ruby:2.7-buster-node - image: circleci/ruby:2.7-buster-node
environment: &ruby_environment environment: &ruby_environment
BUNDLE_JOBS: 3
BUNDLE_RETRY: 3
BUNDLE_APP_CONFIG: ./.bundle/ BUNDLE_APP_CONFIG: ./.bundle/
BUNDLE_PATH: ./vendor/bundle/ BUNDLE_PATH: ./vendor/bundle/
DB_HOST: localhost DB_HOST: localhost
DB_USER: root DB_USER: root
RAILS_ENV: test RAILS_ENV: test
PARALLEL_TEST_PROCESSORS: 4
ALLOW_NOPAM: true ALLOW_NOPAM: true
CONTINUOUS_INTEGRATION: true CONTINUOUS_INTEGRATION: true
DISABLE_SIMPLECOV: true DISABLE_SIMPLECOV: true
@ -32,9 +33,9 @@ aliases:
- &restore_ruby_dependencies - &restore_ruby_dependencies
restore_cache: restore_cache:
keys: keys:
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
- v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}- - v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-
- v2-ruby-dependencies- - v3-ruby-dependencies-
- &install_steps - &install_steps
steps: steps:
@ -42,11 +43,13 @@ aliases:
- *attach_workspace - *attach_workspace
- restore_cache: - restore_cache:
keys: keys:
- v1-node-dependencies-{{ checksum "yarn.lock" }} - v2-node-dependencies-{{ checksum "yarn.lock" }}
- v1-node-dependencies- - v2-node-dependencies-
- run: yarn install --frozen-lockfile - run:
name: Install yarn dependencies
command: yarn install --frozen-lockfile
- save_cache: - save_cache:
key: v1-node-dependencies-{{ checksum "yarn.lock" }} key: v2-node-dependencies-{{ checksum "yarn.lock" }}
paths: paths:
- ./node_modules/ - ./node_modules/
- *persist_to_workspace - *persist_to_workspace
@ -58,26 +61,27 @@ aliases:
sudo apt-get update sudo apt-get update
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler 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 - &install_ruby_dependencies
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version - run:
name: Set Ruby version
command: ruby -e 'puts RUBY_VERSION' | tee /tmp/.ruby-version
- *restore_ruby_dependencies - *restore_ruby_dependencies
- run: bundle config set clean 'true' - run:
- run: bundle config set deployment 'true' name: Set bundler settings
- run: bundle config set with 'pam_authentication' command: |
- run: bundle config set without 'development production' bundle config clean 'true'
- run: bundle config set frozen 'true' bundle config deployment 'true'
- run: bundle install --jobs 16 --retry 3 && bundle clean bundle config with 'pam_authentication'
bundle config without 'development production'
bundle config frozen 'true'
- run:
name: Install bundler dependencies
command: bundle check || (bundle install && bundle clean)
- save_cache: - save_cache:
key: v2-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }} key: v3-ruby-dependencies-{{ checksum "/tmp/.ruby-version" }}-{{ checksum "Gemfile.lock" }}
paths: paths:
- ./.bundle/ - ./.bundle/
- ./vendor/bundle/ - ./vendor/bundle/
@ -88,17 +92,26 @@ aliases:
- ./mastodon/vendor/bundle/ - ./mastodon/vendor/bundle/
- &test_steps - &test_steps
parallelism: 4
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: sudo apt-get install -y ffmpeg
- run: - run:
name: Prepare Tests name: Install FFMPEG
command: ./bin/rails parallel:create parallel:load_schema parallel:prepare command: sudo apt-get install -y ffmpeg
- run: - run:
name: Run Tests name: Load database schema
command: ./bin/retry bundle exec parallel_test ./spec/ --group-by filesize --type rspec command: ./bin/rails db:create db:schema:load db:seed
- run:
name: Run rspec in parallel
command: |
bundle exec rspec --profile 10 \
--format RspecJunitFormatter \
--out test_results/rspec.xml \
--format progress \
$(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
- store_test_results:
path: test_results
jobs: jobs:
install: install:
<<: *defaults <<: *defaults
@ -115,19 +128,14 @@ jobs:
environment: *ruby_environment environment: *ruby_environment
<<: *install_ruby_dependencies <<: *install_ruby_dependencies
install-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5-buster-node
environment: *ruby_environment
<<: *install_ruby_dependencies
build: build:
<<: *defaults <<: *defaults
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: ./bin/rails assets:precompile - run:
name: Precompile assets
command: ./bin/rails assets:precompile
- persist_to_workspace: - persist_to_workspace:
root: ~/projects/ root: ~/projects/
paths: paths:
@ -149,10 +157,10 @@ jobs:
- *install_system_dependencies - *install_system_dependencies
- run: - run:
name: Create database name: Create database
command: ./bin/rails parallel:create command: ./bin/rails db:create
- run: - run:
name: Run migrations name: Run migrations
command: ./bin/rails parallel:migrate command: ./bin/rails db:migrate
test-ruby2.7: test-ruby2.7:
<<: *defaults <<: *defaults
@ -178,35 +186,33 @@ jobs:
- image: circleci/redis:5-alpine - image: circleci/redis:5-alpine
<<: *test_steps <<: *test_steps
test-ruby2.5:
<<: *defaults
docker:
- image: circleci/ruby:2.5-buster-node
environment: *ruby_environment
- image: circleci/postgres:12.2
environment:
POSTGRES_USER: root
POSTGRES_HOST_AUTH_METHOD: trust
- image: circleci/redis:5-alpine
<<: *test_steps
test-webui: test-webui:
<<: *defaults <<: *defaults
docker: docker:
- image: circleci/node:12-buster - image: circleci/node:12-buster
steps: steps:
- *attach_workspace - *attach_workspace
- run: ./bin/retry yarn test:jest - run:
name: Run jest
command: yarn test:jest
check-i18n: check-i18n:
<<: *defaults <<: *defaults
steps: steps:
- *attach_workspace - *attach_workspace
- *install_system_dependencies - *install_system_dependencies
- run: bundle exec i18n-tasks check-normalized - run:
- run: bundle exec i18n-tasks unused -l en name: Check locale file normalization
- run: bundle exec i18n-tasks check-consistent-interpolations command: bundle exec i18n-tasks check-normalized
- run: bundle exec rake repo:check_locales_files - run:
name: Check for unused strings
command: bundle exec i18n-tasks unused -l en
- run:
name: Check for wrong string interpolations
command: bundle exec i18n-tasks check-consistent-interpolations
- run:
name: Check that all required locale files exist
command: bundle exec rake repo:check_locales_files
workflows: workflows:
version: 2 version: 2
@ -220,10 +226,6 @@ workflows:
requires: requires:
- install - install
- install-ruby2.7 - install-ruby2.7
- install-ruby2.5:
requires:
- install
- install-ruby2.7
- build: - build:
requires: requires:
- install-ruby2.7 - install-ruby2.7
@ -238,10 +240,6 @@ workflows:
requires: requires:
- install-ruby2.6 - install-ruby2.6
- build - build
- test-ruby2.5:
requires:
- install-ruby2.5
- build
- test-webui: - test-webui:
requires: requires:
- install - install

View File

@ -20,7 +20,7 @@ gem 'makara', '~> 0.4'
gem 'pghero', '~> 2.4' gem 'pghero', '~> 2.4'
gem 'dotenv-rails', '~> 2.7' gem 'dotenv-rails', '~> 2.7'
gem 'aws-sdk-s3', '~> 1.61', require: false gem 'aws-sdk-s3', '~> 1.63', require: false
gem 'fog-core', '<= 2.1.0' gem 'fog-core', '<= 2.1.0'
gem 'fog-openstack', '~> 0.3', require: false gem 'fog-openstack', '~> 0.3', require: false
gem 'paperclip', '~> 6.0' gem 'paperclip', '~> 6.0'
@ -129,6 +129,7 @@ group :test do
gem 'simplecov', '~> 0.18', require: false gem 'simplecov', '~> 0.18', require: false
gem 'webmock', '~> 3.8' gem 'webmock', '~> 3.8'
gem 'parallel_tests', '~> 2.32' gem 'parallel_tests', '~> 2.32'
gem 'rspec_junit_formatter', '~> 0.4'
end end
group :development do group :development do

View File

@ -92,7 +92,7 @@ GEM
av (0.9.0) av (0.9.0)
cocaine (~> 0.5.3) cocaine (~> 0.5.3)
aws-eventstream (1.1.0) aws-eventstream (1.1.0)
aws-partitions (1.296.0) aws-partitions (1.303.0)
aws-sdk-core (3.94.0) aws-sdk-core (3.94.0)
aws-eventstream (~> 1, >= 1.0.2) aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.239.0) aws-partitions (~> 1, >= 1.239.0)
@ -101,7 +101,7 @@ GEM
aws-sdk-kms (1.30.0) aws-sdk-kms (1.30.0)
aws-sdk-core (~> 3, >= 3.71.0) aws-sdk-core (~> 3, >= 3.71.0)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.61.2) aws-sdk-s3 (1.63.0)
aws-sdk-core (~> 3, >= 3.83.0) aws-sdk-core (~> 3, >= 3.83.0)
aws-sdk-kms (~> 1) aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.1) aws-sigv4 (~> 1.1)
@ -277,7 +277,7 @@ GEM
http-parser (~> 1.2.0) http-parser (~> 1.2.0)
http-cookie (1.0.3) http-cookie (1.0.3)
domain_name (~> 0.5) domain_name (~> 0.5)
http-form_data (2.2.0) http-form_data (2.3.0)
http-parser (1.2.1) http-parser (1.2.1)
ffi-compiler (>= 1.0, < 2.0) ffi-compiler (>= 1.0, < 2.0)
http_accept_language (2.1.1) http_accept_language (2.1.1)
@ -303,7 +303,7 @@ 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.2) json-ld (3.1.3)
htmlentities (~> 4.3) htmlentities (~> 4.3)
json-canonicalization (~> 0.2) json-canonicalization (~> 0.2)
link_header (~> 0.0, >= 0.0.8) link_header (~> 0.0, >= 0.0.8)
@ -359,7 +359,7 @@ GEM
nokogiri (~> 1.10) nokogiri (~> 1.10)
mime-types (3.3.1) mime-types (3.3.1)
mime-types-data (~> 3.2015) mime-types-data (~> 3.2015)
mime-types-data (3.2019.1009) mime-types-data (3.2020.0425)
mimemagic (0.3.4) mimemagic (0.3.4)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.4.0) mini_portile2 (2.4.0)
@ -382,7 +382,7 @@ GEM
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
sidekiq (>= 3.5) sidekiq (>= 3.5)
statsd-ruby (~> 1.4, >= 1.4.0) statsd-ruby (~> 1.4, >= 1.4.0)
oj (3.10.5) oj (3.10.6)
omniauth (1.9.1) omniauth (1.9.1)
hashie (>= 3.4.6) hashie (>= 3.4.6)
rack (>= 1.6.2, < 3) rack (>= 1.6.2, < 3)
@ -409,7 +409,7 @@ GEM
parallel parallel
parser (2.7.1.1) parser (2.7.1.1)
ast (~> 2.4.0) ast (~> 2.4.0)
parslet (1.8.2) parslet (2.0.0)
pastel (0.7.3) pastel (0.7.3)
equatable (~> 0.6) equatable (~> 0.6)
tty-color (~> 0.5) tty-color (~> 0.5)
@ -542,6 +542,8 @@ GEM
rspec-core (~> 3.0, >= 3.0.0) rspec-core (~> 3.0, >= 3.0.0)
sidekiq (>= 2.4.0) sidekiq (>= 2.4.0)
rspec-support (3.9.2) rspec-support (3.9.2)
rspec_junit_formatter (0.4.1)
rspec-core (>= 2, < 4, != 2.12.0)
rubocop (0.79.0) rubocop (0.79.0)
jaro_winkler (~> 1.5.1) jaro_winkler (~> 1.5.1)
parallel (~> 1.10) parallel (~> 1.10)
@ -554,7 +556,7 @@ GEM
rack (>= 1.1) rack (>= 1.1)
rubocop (>= 0.72.0) rubocop (>= 0.72.0)
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby-saml (1.9.0) ruby-saml (1.11.0)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
rufus-scheduler (3.6.0) rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6) fugit (~> 1.1, >= 1.1.6)
@ -668,7 +670,7 @@ DEPENDENCIES
active_record_query_trace (~> 1.7) active_record_query_trace (~> 1.7)
addressable (~> 2.7) addressable (~> 2.7)
annotate (~> 3.1) annotate (~> 3.1)
aws-sdk-s3 (~> 1.61) aws-sdk-s3 (~> 1.63)
better_errors (~> 2.6) better_errors (~> 2.6)
binding_of_caller (~> 0.7) binding_of_caller (~> 0.7)
blurhash (~> 0.1) blurhash (~> 0.1)
@ -765,6 +767,7 @@ DEPENDENCIES
rqrcode (~> 1.1) rqrcode (~> 1.1)
rspec-rails (~> 4.0) rspec-rails (~> 4.0)
rspec-sidekiq (~> 3.0) rspec-sidekiq (~> 3.0)
rspec_junit_formatter (~> 0.4)
rubocop (~> 0.79) rubocop (~> 0.79)
rubocop-rails (~> 2.5) rubocop-rails (~> 2.5)
ruby-progressbar (~> 1.10) ruby-progressbar (~> 1.10)

View File

@ -28,7 +28,7 @@ class AccountsController < ApplicationController
end end
@pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses? @pinned_statuses = cache_collection(@account.pinned_statuses, Status) if show_pinned_statuses?
@statuses = filtered_status_page(params) @statuses = filtered_status_page
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
@rss_url = rss_url @rss_url = rss_url
@ -141,12 +141,12 @@ class AccountsController < ApplicationController
request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize) request.path.split('.').first.ends_with?(Addressable::URI.parse("/tagged/#{params[:tag]}").normalize)
end end
def filtered_status_page(params) def filtered_status_page
if params[:min_id].present? filtered_statuses.paginate_by_id(PAGE_SIZE, params_slice(:max_id, :min_id, :since_id))
filtered_statuses.paginate_by_min_id(PAGE_SIZE, params[:min_id]).reverse
else
filtered_statuses.paginate_by_max_id(PAGE_SIZE, params[:max_id], params[:since_id]).to_a
end end
def params_slice(*keys)
params.slice(*keys).permit(*keys)
end end
def restrict_fields_to def restrict_fields_to

View File

@ -24,20 +24,23 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
def set_size def set_size
case params[:id] case params[:id]
when 'featured' when 'featured'
@account.pinned_statuses.count @size = @account.pinned_statuses.count
else else
raise ActiveRecord::RecordNotFound not_found
end end
end end
def scope_for_collection def scope_for_collection
case params[:id] case params[:id]
when 'featured' when 'featured'
return Status.none if @account.blocking?(signed_request_account) # Because in public fetch mode we cache the response, there would be no
# benefit from performing the check below, since a blocked account or domain
@account.pinned_statuses # would likely be served the cache from the reverse proxy anyway
if authorized_fetch_mode? && !signed_request_account.nil? && (@account.blocking?(signed_request_account) || (!signed_request_account.domain.nil? && @account.domain_blocking?(signed_request_account.domain)))
Status.none
else else
raise ActiveRecord::RecordNotFound @account.pinned_statuses
end
end end
end end

View File

@ -11,7 +11,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
before_action :set_cache_headers before_action :set_cache_headers
def show def show
expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode?) expires_in(page_requested? ? 0 : 3.minutes, public: public_fetch_mode? && !(signed_request_account.present? && page_requested?))
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json' render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end end
@ -50,12 +50,12 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
return unless page_requested? return unless page_requested?
@statuses = @account.statuses.permitted_for(@account, signed_request_account) @statuses = @account.statuses.permitted_for(@account, signed_request_account)
@statuses = params[:min_id].present? ? @statuses.paginate_by_min_id(LIMIT, params[:min_id]).reverse : @statuses.paginate_by_max_id(LIMIT, params[:max_id]) @statuses = @statuses.paginate_by_id(LIMIT, params_slice(:max_id, :min_id, :since_id))
@statuses = cache_collection(@statuses, Status) @statuses = cache_collection(@statuses, Status)
end end
def page_requested? def page_requested?
params[:page] == 'true' truthy_param?(:page)
end end
def page_params def page_params

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class ActivityPub::RepliesController < ActivityPub::BaseController class ActivityPub::RepliesController < ActivityPub::BaseController
include SignatureAuthentication include SignatureVerification
include Authorization include Authorization
include AccountOwnedConcern include AccountOwnedConcern
@ -19,15 +19,19 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
private private
def pundit_user
signed_request_account
end
def set_status def set_status
@status = @account.statuses.find(params[:status_id]) @status = @account.statuses.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def set_replies def set_replies
@replies = page_params[:only_other_accounts] ? Status.where.not(account_id: @account.id) : @account.statuses @replies = only_other_accounts? ? Status.where.not(account_id: @account.id) : @account.statuses
@replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted]) @replies = @replies.where(in_reply_to_id: @status.id, visibility: [:public, :unlisted])
@replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id]) @replies = @replies.paginate_by_min_id(DESCENDANTS_LIMIT, params[:min_id])
end end
@ -38,7 +42,7 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
type: :unordered, type: :unordered,
part_of: account_status_replies_url(@account, @status), part_of: account_status_replies_url(@account, @status),
next: next_page, next: next_page,
items: @replies.map { |status| status.local ? status : status.uri } items: @replies.map { |status| status.local? ? status : status.uri }
) )
return page if page_requested? return page if page_requested?
@ -51,16 +55,21 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
end end
def page_requested? def page_requested?
params[:page] == 'true' truthy_param?(:page)
end
def only_other_accounts?
truthy_param?(:only_other_accounts)
end end
def next_page def next_page
only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT) only_other_accounts = !(@replies&.last&.account_id == @account.id && @replies.size == DESCENDANTS_LIMIT)
account_status_replies_url( account_status_replies_url(
@account, @account,
@status, @status,
page: true, page: true,
min_id: only_other_accounts && !page_params[:only_other_accounts] ? nil : @replies&.last&.id, min_id: only_other_accounts && !only_other_accounts? ? nil : @replies&.last&.id,
only_other_accounts: only_other_accounts only_other_accounts: only_other_accounts
) )
end end

View File

@ -18,7 +18,7 @@ class Api::V1::Polls::VotesController < Api::BaseController
@poll = Poll.attached.find(params[:poll_id]) @poll = Poll.attached.find(params[:poll_id])
authorize @poll.status, :show? authorize @poll.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def vote_params def vote_params

View File

@ -17,7 +17,7 @@ class Api::V1::PollsController < Api::BaseController
@poll = Poll.attached.find(params[:id]) @poll = Poll.attached.find(params[:id])
authorize @poll.status, :show? authorize @poll.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def refresh_poll def refresh_poll

View File

@ -4,6 +4,7 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
before_action -> { doorkeeper_authorize! :push } before_action -> { doorkeeper_authorize! :push }
before_action :require_user! before_action :require_user!
before_action :set_web_push_subscription before_action :set_web_push_subscription
before_action :check_web_push_subscription, only: [:show, :update]
def create def create
@web_subscription&.destroy! @web_subscription&.destroy!
@ -21,16 +22,11 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
end end
def show def show
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end end
def update def update
raise ActiveRecord::RecordNotFound if @web_subscription.nil?
@web_subscription.update!(data: data_params) @web_subscription.update!(data: data_params)
render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer render json: @web_subscription, serializer: REST::WebPushSubscriptionSerializer
end end
@ -45,12 +41,17 @@ class Api::V1::Push::SubscriptionsController < Api::BaseController
@web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id) @web_subscription = ::Web::PushSubscription.find_by(access_token_id: doorkeeper_token.id)
end end
def check_web_push_subscription
not_found if @web_subscription.nil?
end
def subscription_params def subscription_params
params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh]) params.require(:subscription).permit(:endpoint, keys: [:auth, :p256dh])
end end
def data_params def data_params
return {} if params[:data].blank? return {} if params[:data].blank?
params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll]) params.require(:data).permit(alerts: [:follow, :follow_request, :favourite, :reblog, :mention, :poll])
end end
end end

View File

@ -28,8 +28,7 @@ class Api::V1::Statuses::MutesController < Api::BaseController
@status = Status.find(params[:status_id]) @status = Status.find(params[:status_id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
# Reraise in order to get a 404 instead of a 403 error code not_found
raise ActiveRecord::RecordNotFound
end end
def set_conversation def set_conversation

View File

@ -68,7 +68,7 @@ class Api::V1::StatusesController < Api::BaseController
@status = Status.find(params[:id]) @status = Status.find(params[:id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def set_thread def set_thread

View File

@ -33,7 +33,7 @@ class MediaController < ApplicationController
def verify_permitted_status! def verify_permitted_status!
authorize @media_attachment.status, :show? authorize @media_attachment.status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def check_playable def check_playable

View File

@ -42,7 +42,7 @@ class RemoteInteractionController < ApplicationController
@status = Status.find(params[:id]) @status = Status.find(params[:id])
authorize @status, :show? authorize @status, :show?
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
raise ActiveRecord::RecordNotFound not_found
end end
def set_body_classes def set_body_classes

View File

@ -49,7 +49,7 @@ class StatusesController < ApplicationController
def embed def embed
use_pack 'embed' use_pack 'embed'
return not_found if @status.hidden? return not_found if @status.hidden? || @status.reblog?
expires_in 180, public: true expires_in 180, public: true
response.headers['X-Frame-Options'] = 'ALLOWALL' response.headers['X-Frame-Options'] = 'ALLOWALL'

View File

@ -10,7 +10,7 @@ delegate(document, '#account_display_name', 'input', ({ target }) => {
if (target.value) { if (target.value) {
name.innerHTML = emojify(escapeTextContentForBrowser(target.value)); name.innerHTML = emojify(escapeTextContentForBrowser(target.value));
} else { } else {
name.textContent = document.querySelector('#default_account_display_name').textContent; name.textContent = name.textContent = target.dataset.default;
} }
} }
}); });

View File

@ -99,15 +99,13 @@ function main() {
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
delegate(document, '.status__content__spoiler-link', 'click', function() { delegate(document, '.status__content__spoiler-link', 'click', function() {
const contentEl = this.parentNode.parentNode.querySelector('.e-content'); const statusEl = this.parentNode.parentNode;
if (contentEl.style.display === 'block') { if (statusEl.dataset.spoiler === 'expanded') {
contentEl.style.display = 'none'; statusEl.dataset.spoiler = 'folded';
this.parentNode.style.marginBottom = 0;
this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
} else { } else {
contentEl.style.display = 'block'; statusEl.dataset.spoiler = 'expanded';
this.parentNode.style.marginBottom = null;
this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
} }
@ -115,8 +113,8 @@ function main() {
}); });
[].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); const statusEl = spoilerLink.parentNode.parentNode;
const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
}); });
}); });

View File

@ -42,7 +42,7 @@ export function updateTimeline(timeline, status, accept) {
export function deleteFromTimelines(id) { export function deleteFromTimelines(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account']); const accountId = getState().getIn(['statuses', id, 'account']);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]); const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => status.get('id'));
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);
dispatch({ dispatch({

View File

@ -46,7 +46,7 @@ class DropdownMenu extends React.PureComponent {
document.addEventListener('keydown', this.handleKeyDown, false); document.addEventListener('keydown', this.handleKeyDown, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem && this.props.openedViaKeyboard) { if (this.focusedItem && this.props.openedViaKeyboard) {
this.focusedItem.focus(); this.focusedItem.focus({ preventScroll: true });
} }
this.setState({ mounted: true }); this.setState({ mounted: true });
} }

View File

@ -100,7 +100,7 @@ class PrivacyDropdownMenu extends React.PureComponent {
componentDidMount () { componentDidMount () {
document.addEventListener('click', this.handleDocumentClick, false); document.addEventListener('click', this.handleDocumentClick, false);
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions); document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
if (this.focusedItem) this.focusedItem.focus(); if (this.focusedItem) this.focusedItem.focus({ preventScroll: true });
this.setState({ mounted: true }); this.setState({ mounted: true });
} }

View File

@ -25,7 +25,7 @@ const importStatuses = (state, statuses) =>
const deleteStatus = (state, id, references) => { const deleteStatus = (state, id, references) => {
references.forEach(ref => { references.forEach(ref => {
state = deleteStatus(state, ref[0], []); state = deleteStatus(state, ref, []);
}); });
return state.delete(id); return state.delete(id);

View File

@ -89,7 +89,7 @@ const updateTimeline = (state, timeline, status, usePendingItems) => {
})); }));
}; };
const deleteStatus = (state, id, accountId, references, exclude_account = null) => { const deleteStatus = (state, id, references, exclude_account = null) => {
state.keySeq().forEach(timeline => { state.keySeq().forEach(timeline => {
if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) { if (exclude_account === null || (timeline !== `account:${exclude_account}` && !timeline.startsWith(`account:${exclude_account}:`))) {
const helper = list => list.filterNot(item => item === id); const helper = list => list.filterNot(item => item === id);
@ -99,7 +99,7 @@ const deleteStatus = (state, id, accountId, references, exclude_account = null)
// Remove reblogs of deleted status // Remove reblogs of deleted status
references.forEach(ref => { references.forEach(ref => {
state = deleteStatus(state, ref[0], ref[1], [], exclude_account); state = deleteStatus(state, ref, [], exclude_account);
}); });
return state; return state;
@ -117,8 +117,8 @@ const filterTimelines = (state, relationship, statuses) => {
return; return;
} }
references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => [item.get('id'), item.get('account')]); references = statuses.filter(item => item.get('reblog') === status.get('id')).map(item => item.get('id'));
state = deleteStatus(state, status.get('id'), status.get('account'), references, relationship.id); state = deleteStatus(state, status.get('id'), references, relationship.id);
}); });
return state; return state;
@ -150,7 +150,7 @@ export default function timelines(state = initialState, action) {
case TIMELINE_UPDATE: case TIMELINE_UPDATE:
return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems); return updateTimeline(state, action.timeline, fromJS(action.status), action.usePendingItems);
case TIMELINE_DELETE: case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.accountId, action.references, action.reblogOf); return deleteStatus(state, action.id, action.references, action.reblogOf);
case TIMELINE_CLEAR: case TIMELINE_CLEAR:
return clearTimeline(state, action.timeline); return clearTimeline(state, action.timeline);
case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_BLOCK_SUCCESS:

View File

@ -103,15 +103,13 @@ function main() {
delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static')); delegate(document, '.custom-emoji', 'mouseout', getEmojiAnimationHandler('data-static'));
delegate(document, '.status__content__spoiler-link', 'click', function() { delegate(document, '.status__content__spoiler-link', 'click', function() {
const contentEl = this.parentNode.parentNode.querySelector('.e-content'); const statusEl = this.parentNode.parentNode;
if (contentEl.style.display === 'block') { if (statusEl.dataset.spoiler === 'expanded') {
contentEl.style.display = 'none'; statusEl.dataset.spoiler = 'folded';
this.parentNode.style.marginBottom = 0;
this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format(); this.textContent = (new IntlMessageFormat(messages['status.show_more'] || 'Show more', locale)).format();
} else { } else {
contentEl.style.display = 'block'; statusEl.dataset.spoiler = 'expanded';
this.parentNode.style.marginBottom = null;
this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format(); this.textContent = (new IntlMessageFormat(messages['status.show_less'] || 'Show less', locale)).format();
} }
@ -119,8 +117,8 @@ function main() {
}); });
[].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => { [].forEach.call(document.querySelectorAll('.status__content__spoiler-link'), (spoilerLink) => {
const contentEl = spoilerLink.parentNode.parentNode.querySelector('.e-content'); const statusEl = spoilerLink.parentNode.parentNode;
const message = (contentEl.style.display === 'block') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more'); const message = (statusEl.dataset.spoiler === 'expanded') ? (messages['status.show_less'] || 'Show less') : (messages['status.show_more'] || 'Show more');
spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format(); spoilerLink.textContent = (new IntlMessageFormat(message, locale)).format();
}); });
}); });

View File

@ -757,8 +757,13 @@ $small-breakpoint: 960px;
} }
} }
&__counters__wrapper {
display: flex;
}
&__counter { &__counter {
padding: 10px; padding: 10px;
width: 50%;
strong { strong {
font-family: $font-display, sans-serif; font-family: $font-display, sans-serif;

View File

@ -583,6 +583,18 @@ body,
} }
} }
.special-action-button,
.back-link {
text-align: right;
flex: 1 1 auto;
}
.action-buttons {
display: flex;
overflow: hidden;
justify-content: space-between;
}
.spacer { .spacer {
flex: 1 1 auto; flex: 1 1 auto;
} }
@ -920,3 +932,11 @@ a.name-tag,
} }
} }
} }
.account-badges {
margin: -2px 0;
}
.dashboard__counters.admin-account-counters {
margin-top: 10px;
}

View File

@ -229,3 +229,19 @@ button {
} }
} }
} }
.logo-resources {
display: none;
}
// NoScript adds a __ns__pop2top class to the full ancestry of blocked elements,
// to set the z-index to a high value, which messes with modals and dropdowns.
// Blocked elements can in theory only be media and frames/embeds, so they
// should only appear in statuses, under divs and articles.
body,
div,
article {
.__ns__pop2top {
z-index: unset !important;
}
}

View File

@ -1362,6 +1362,12 @@ a .account__avatar {
&-base { &-base {
@include avatar-radius; @include avatar-radius;
@include avatar-size(36px); @include avatar-size(36px);
img {
@include avatar-radius;
width: 100%;
height: 100%;
}
} }
&-overlay { &-overlay {
@ -1372,6 +1378,12 @@ a .account__avatar {
bottom: 0; bottom: 0;
right: 0; right: 0;
z-index: 1; z-index: 1;
img {
@include avatar-radius;
width: 100%;
height: 100%;
}
} }
} }

View File

@ -142,6 +142,10 @@ code {
} }
} }
.otp-hint {
margin-bottom: 25px;
}
.card { .card {
margin-bottom: 15px; margin-bottom: 15px;
} }
@ -285,6 +289,14 @@ code {
margin-bottom: 25px; margin-bottom: 25px;
} }
} }
.fields-group.invited-by {
margin-bottom: 30px;
.hint {
text-align: center;
}
}
} }
.input.radio_buttons .radio label { .input.radio_buttons .radio label {
@ -635,6 +647,15 @@ code {
@media screen and (max-width: 740px) and (min-width: 441px) { @media screen and (max-width: 740px) and (min-width: 441px) {
margin-top: 40px; margin-top: 40px;
} }
&.translation-prompt {
text-align: unset;
color: unset;
a {
text-decoration: underline;
}
}
} }
.form-footer { .form-footer {

View File

@ -19,6 +19,36 @@
} }
} }
progress {
border: 0;
display: block;
width: 100%;
height: 5px;
appearance: none;
background: transparent;
&::-webkit-progress-bar {
background: transparent;
}
// Those rules need to be entirely separate or they won't work, hence the
// duplication
&::-moz-progress-bar {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}
&::-ms-fill {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}
&::-webkit-progress-value {
border-radius: 4px;
background: darken($ui-primary-color, 5%);
}
}
&__option { &__option {
position: relative; position: relative;
display: flex; display: flex;

View File

@ -128,6 +128,16 @@
.embed, .embed,
.public-layout { .public-layout {
.status__content[data-spoiler=folded] {
.e-content {
display: none;
}
p:first-child {
margin-bottom: 0;
}
}
.detailed-status { .detailed-status {
padding: 15px; padding: 15px;
} }
@ -159,5 +169,12 @@
.video-player { .video-player {
margin-top: 10px; margin-top: 10px;
} }
&__action-bar-button {
font-size: 18px;
width: 23.1429px;
height: 23.1429px;
line-height: 23.15px;
}
} }
} }

View File

@ -47,6 +47,8 @@
# suspended_at :datetime # suspended_at :datetime
# trust_level :integer # trust_level :integer
# hide_collections :boolean # hide_collections :boolean
# avatar_storage_schema_version :integer
# header_storage_schema_version :integer
# #
class Account < ApplicationRecord class Account < ApplicationRecord

View File

@ -82,7 +82,7 @@ module Omniauthable
username = starting_username username = starting_username
i = 0 i = 0
while Account.exists?(username: username) while Account.exists?(username: username, domain: nil)
i += 1 i += 1
username = "#{starting_username}_#{i}" username = "#{starting_username}_#{i}"
end end

View File

@ -17,6 +17,7 @@
# image_remote_url :string # image_remote_url :string
# visible_in_picker :boolean default(TRUE), not null # visible_in_picker :boolean default(TRUE), not null
# category_id :bigint(8) # category_id :bigint(8)
# image_storage_schema_version :integer
# #
class CustomEmoji < ApplicationRecord class CustomEmoji < ApplicationRecord

View File

@ -20,6 +20,7 @@
# scheduled_status_id :bigint(8) # scheduled_status_id :bigint(8)
# blurhash :string # blurhash :string
# processing :integer # processing :integer
# file_storage_schema_version :integer
# #
class MediaAttachment < ApplicationRecord class MediaAttachment < ApplicationRecord

View File

@ -22,6 +22,7 @@
# created_at :datetime not null # created_at :datetime not null
# updated_at :datetime not null # updated_at :datetime not null
# embed_url :string default(""), not null # embed_url :string default(""), not null
# image_storage_schema_version :integer
# #
class PreviewCard < ApplicationRecord class PreviewCard < ApplicationRecord
@ -47,6 +48,10 @@ class PreviewCard < ApplicationRecord
before_save :extract_dimensions, if: :link? before_save :extract_dimensions, if: :link?
def local?
false
end
def missing_image? def missing_image?
width.present? && height.present? && image_file_name.blank? width.present? && height.present? && image_file_name.blank?
end end

View File

@ -206,12 +206,8 @@ class Status < ApplicationRecord
def title def title
if destroyed? if destroyed?
"#{account.acct} deleted status" "#{account.acct} deleted status"
elsif reblog?
preview = sensitive ? '<sensitive>' : text.slice(0, 10).split("\n")[0]
"#{account.acct} shared #{reblog.account.acct}'s: #{preview}"
else else
preview = sensitive ? '<sensitive>' : text.slice(0, 20).split("\n")[0] reblog? ? "#{account.acct} shared a status by #{reblog.account.acct}" : "New status by #{account.acct}"
"#{account.acct}: #{preview}"
end end
end end
@ -404,7 +400,7 @@ class Status < ApplicationRecord
if account.nil? if account.nil?
where(visibility: visibility).not_local_only where(visibility: visibility).not_local_only
elsif target_account.blocking?(account) # get rid of blocked peeps elsif target_account.blocking?(account) || (account.domain.present? && target_account.domain_blocking?(account.domain)) # get rid of blocked peeps
none none
elsif account.id == target_account.id # author can see own stuff elsif account.id == target_account.id # author can see own stuff
all all

View File

@ -5,7 +5,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
attributes :uri, :title, :short_description, :description, :email, attributes :uri, :title, :short_description, :description, :email,
:version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits, :version, :urls, :stats, :thumbnail, :max_toot_chars, :poll_limits,
:languages, :registrations, :approval_required :languages, :registrations, :approval_required, :invites_enabled
has_one :contact_account, serializer: REST::AccountSerializer has_one :contact_account, serializer: REST::AccountSerializer
@ -76,6 +76,10 @@ class REST::InstanceSerializer < ActiveModel::Serializer
Setting.registrations_mode == 'approved' Setting.registrations_mode == 'approved'
end end
def invites_enabled
Setting.min_invite_role == 'user'
end
private private
def instance_presenter def instance_presenter

View File

@ -25,7 +25,18 @@ class FetchResourceService < BaseService
end end
def perform_request(&block) def perform_request(&block)
Request.new(:get, @url).add_headers('Accept' => ACCEPT_HEADER).on_behalf_of(Account.representative).perform(&block) Request.new(:get, @url).tap do |request|
request.add_headers('Accept' => ACCEPT_HEADER)
# In a real setting we want to sign all outgoing requests,
# in case the remote server has secure mode enabled and requires
# authentication on all resources. However, during development,
# sending request signatures with an inaccessible host is useless
# and prevents even public resources from being fetched, so
# don't do it
request.on_behalf_of(Account.representative) unless Rails.env.development?
end.perform(&block)
end end
def process_response(response, terminal = false) def process_response(response, terminal = false)

View File

@ -68,11 +68,11 @@
.hero-widget__footer__column .hero-widget__footer__column
%h4= t 'about.server_stats' %h4= t 'about.server_stats'
%div{ style: 'display: flex' } .hero-widget__counters__wrapper
.hero-widget__counter{ style: 'width: 50%' } .hero-widget__counter
%strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true %strong= number_to_human @instance_presenter.user_count, strip_insignificant_zeros: true
%span= t 'about.user_count_after', count: @instance_presenter.user_count %span= t 'about.user_count_after', count: @instance_presenter.user_count
.hero-widget__counter{ style: 'width: 50%' } .hero-widget__counter
%strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true %strong= number_to_human @instance_presenter.active_user_count, strip_insignificant_zeros: true
%span %span
= t 'about.active_count_after' = t 'about.active_count_after'

View File

@ -9,8 +9,10 @@
= link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do = link_to ActivityPub::TagManager.instance.url_for(moved_to_account), class: 'detailed-status__display-name p-author h-card', target: '_blank', rel: 'me noopener noreferrer' do
.detailed-status__display-avatar .detailed-status__display-avatar
.account__avatar-overlay .account__avatar-overlay
.account__avatar-overlay-base{ style: "background-image: url('#{moved_to_account.avatar.url(:original)}')" } .account__avatar-overlay-base
.account__avatar-overlay-overlay{ style: "background-image: url('#{account.avatar.url(:original)}')" } = image_tag moved_to_account.avatar_static_url
.account__avatar-overlay-overlay
= image_tag account.avatar_static_url
%span.display-name %span.display-name
%bdi %bdi

View File

@ -2,7 +2,7 @@
%td %td
= admin_account_link_to(account) = admin_account_link_to(account)
%td %td
%div{ style: 'margin: -2px 0' }= account_badge(account, all: true) %div.account-badges= account_badge(account, all: true)
%td %td
- if account.user_current_sign_in_ip - if account.user_current_sign_in_ip
%samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip %samp.ellipsized-ip{ title: account.user_current_sign_in_ip }= account.user_current_sign_in_ip

View File

@ -31,7 +31,7 @@
%div %div
.account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true) .account__header__content.emojify= Formatter.instance.simplified_format(account, custom_emojify: true)
.dashboard__counters{ style: 'margin-top: 10px' } .dashboard__counters.admin-account-counters
%div %div
= link_to admin_account_statuses_path(@account.id) do = link_to admin_account_statuses_path(@account.id) do
.dashboard__counters__num= number_with_delimiter @account.statuses_count .dashboard__counters__num= number_with_delimiter @account.statuses_count
@ -178,18 +178,8 @@
= @account.shared_inbox_url = @account.shared_inbox_url
= fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times' = fa_icon DeliveryFailureTracker.available?(@account.shared_inbox_url) ? 'check': 'times'
%div{ style: 'overflow: hidden' } %div.action-buttons
%div{ style: 'float: right' } %div
- if @account.local?
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
- if @account.user&.otp_required_for_login?
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
- if !@account.memorial? && @account.user_approved?
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
- else
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
%div{ style: 'float: left' }
- if @account.local? && @account.user_approved? - if @account.local? && @account.user_approved?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account) = link_to t('admin.accounts.warn'), new_admin_account_action_path(@account.id, type: 'none'), class: 'button' if can?(:warn, @account)
- if @account.silenced? - if @account.silenced?
@ -216,6 +206,16 @@
- 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'
%div
- if @account.local?
= link_to t('admin.accounts.reset_password'), admin_account_reset_path(@account.id), method: :create, class: 'button' if can?(:reset_password, @account.user)
- if @account.user&.otp_required_for_login?
= link_to t('admin.accounts.disable_two_factor_authentication'), admin_user_two_factor_authentication_path(@account.user.id), method: :delete, class: 'button' if can?(:disable_2fa, @account.user)
- if !@account.memorial? && @account.user_approved?
= link_to t('admin.accounts.memorialize'), memorialize_admin_account_path(@account.id), method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive' if can?(:memorialize, @account)
- else
= link_to t('admin.accounts.redownload'), redownload_admin_account_path(@account.id), method: :post, class: 'button' if can?(:redownload, @account)
%hr.spacer/ %hr.spacer/
- unless @warnings.empty? - unless @warnings.empty?

View File

@ -10,7 +10,7 @@
- unless whitelist_mode? - unless whitelist_mode?
%li= filter_link_to t('admin.instances.moderation.limited'), limited: '1' %li= filter_link_to t('admin.instances.moderation.limited'), limited: '1'
%div{ style: 'flex: 1 1 auto; text-align: right' } %div.special-action-button
- if whitelist_mode? - if whitelist_mode?
= link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button' = link_to t('admin.domain_allows.add_new'), new_admin_domain_allow_path, class: 'button'
- else - else

View File

@ -45,11 +45,11 @@
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div.action-buttons
%div{ style: 'float: left' } %div
= link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button' = link_to t('admin.accounts.title'), admin_accounts_path(remote: '1', by_domain: @instance.domain), class: 'button'
%div{ style: 'float: right' } %div
- if @domain_allow - if @domain_allow
= link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete } = link_to t('admin.domain_allows.undo'), admin_domain_allow_path(@domain_allow), class: 'button button--destructive', data: { confirm: t('admin.accounts.are_you_sure'), method: :delete }
- elsif @domain_block - elsif @domain_block

View File

@ -22,9 +22,9 @@
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div.action-buttons
%div{ style: 'float: right' }
= link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
%div %div
= link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' = link_to t('admin.accounts.approve_all'), approve_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
%div
= link_to t('admin.accounts.reject_all'), reject_all_admin_pending_accounts_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'

View File

@ -17,7 +17,7 @@
%li= filter_link_to t('admin.accounts.location.local'), location: 'local' %li= filter_link_to t('admin.accounts.location.local'), location: 'local'
%li= filter_link_to t('admin.accounts.location.remote'), location: 'remote' %li= filter_link_to t('admin.accounts.location.remote'), location: 'remote'
.back-link{ style: 'flex: 1 1 auto; text-align: right' } .back-link
= link_to admin_account_path(@account.id) do = link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw' = fa_icon 'chevron-left fw'
= t('admin.statuses.back_to_account') = t('admin.statuses.back_to_account')

View File

@ -65,9 +65,11 @@
%hr.spacer %hr.spacer
%div{ style: 'overflow: hidden; margin-bottom: 20px; clear: both' } %div.action-buttons
%div
- if @report.unresolved? - if @report.unresolved?
%div{ style: 'float: right' } %div
- if @report.target_account.local? - if @report.target_account.local?
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button' = link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive' = link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'

View File

@ -9,7 +9,7 @@
%ul %ul
%li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected' %li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected' %li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
.back-link{ style: 'flex: 1 1 auto; text-align: right' } .back-link
= link_to admin_account_path(@account.id) do = link_to admin_account_path(@account.id) do
= fa_icon 'chevron-left fw' = fa_icon 'chevron-left fw'
= t('admin.statuses.back_to_account') = t('admin.statuses.back_to_account')

View File

@ -4,7 +4,7 @@
= "@#{@account.acct}" = "@#{@account.acct}"
.filters .filters
.back-link{ style: 'flex: 1 1 auto; text-align: right' } .back-link
= link_to admin_account_path(@account.id) do = link_to admin_account_path(@account.id) do
%i.fa.fa-chevron-left.fa-fw %i.fa.fa-chevron-left.fa-fw
= t('admin.statuses.back_to_account') = t('admin.statuses.back_to_account')

View File

@ -68,9 +68,9 @@
- if params[:pending_review] == '1' || params[:unreviewed] == '1' - if params[:pending_review] == '1' || params[:unreviewed] == '1'
%hr.spacer/ %hr.spacer/
%div{ style: 'overflow: hidden' } %div.action-buttons
%div{ style: 'float: right' }
= link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'
%div %div
= link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button' = link_to t('admin.accounts.approve_all'), approve_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button'
%div
= link_to t('admin.accounts.reject_all'), reject_all_admin_tags_path, method: :post, data: { confirm: t('admin.accounts.are_you_sure') }, class: 'button button--destructive'

View File

@ -9,7 +9,6 @@
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
.display-name .display-name
%span{ id: "default_account_display_name", style: "display: none" }= account.username
%bdi %bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true) %strong.emojify.p-name= display_name(account, custom_emojify: true)
%span %span

View File

@ -8,8 +8,8 @@
= render 'shared/error_messages', object: resource = render 'shared/error_messages', object: resource
- if @invite.present? && @invite.autofollow? - if @invite.present? && @invite.autofollow?
.fields-group{ style: 'margin-bottom: 30px' } .fields-group.invited-by
%p.hint{ style: 'text-align: center' }= t('invites.invited_by') %p.hint= t('invites.invited_by')
= render 'application/card', account: @invite.user.account = render 'application/card', account: @invite.user.account
= f.simple_fields_for :account do |ff| = f.simple_fields_for :account do |ff|

View File

@ -2,7 +2,7 @@
= t('auth.login') = t('auth.login')
= simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f| = simple_form_for(resource, as: resource_name, url: session_path(resource_name), method: :post) do |f|
%p.hint{ style: 'margin-bottom: 25px' }= t('simple_form.hints.sessions.otp') %p.hint.otp-hint= t('simple_form.hints.sessions.otp')
.fields-group .fields-group
= f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, autofocus: true = f.input :otp_attempt, type: :number, wrapper: :with_label, label: t('simple_form.labels.defaults.otp_attempt'), input_html: { 'aria-label' => t('simple_form.labels.defaults.otp_attempt'), :autocomplete => 'off' }, autofocus: true

View File

@ -28,7 +28,6 @@
= image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo' = image_tag account.avatar.url, alt: '', width: 48, height: 48, class: 'u-photo'
.display-name .display-name
%span{ id: "default_account_display_name", style: "display: none" }= account.username
%bdi %bdi
%strong.emojify.p-name= display_name(account, custom_emojify: true) %strong.emojify.p-name= display_name(account, custom_emojify: true)
%span= acct(account) %span= acct(account)

View File

@ -40,6 +40,6 @@
%body{ class: body_classes } %body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield = content_for?(:content) ? yield(:content) : yield
%div{ style: 'display: none'} .logo-resources
= render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')
= render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg') = render file: Rails.root.join('app', 'javascript', 'images', 'logo_full.svg')

View File

@ -23,5 +23,5 @@
%body.embed %body.embed
= yield = yield
%div{ style: 'display: none'} .logo-resources
= render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg') = render file: Rails.root.join('app', 'javascript', 'images', 'logo_transparent.svg')

View File

@ -12,5 +12,5 @@
- else - else
%p= t('about.browse_local_posts') %p= t('about.browse_local_posts')
#mastodon-timeline{ data: { props: Oj.dump(default_props) }} #mastodon-timeline{ data: { props: Oj.dump(default_props.merge(local: !Setting.show_known_fediverse_at_about_page)) }}
#modal-container #modal-container

View File

@ -9,8 +9,8 @@
= 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 = 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 - unless I18n.locale == :en
.flash-message{ style: "text-align: unset; color: unset" } .flash-message.translation-prompt
#{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")} #{t 'appearance.localization.body'} #{content_tag(:a, t('appearance.localization.guide_link_text'), href: t('appearance.localization.guide_link'), target: "_blank", rel: "noopener")}
%h4= t 'appearance.advanced_web_interface' %h4= t 'appearance.advanced_web_interface'

View File

@ -9,7 +9,7 @@
.fields-row .fields-row
.fields-row__column.fields-group.fields-row__column-6 .fields-row__column.fields-group.fields-row__column-6
= f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH }, hint: false = f.input :display_name, wrapper: :with_label, input_html: { maxlength: Account::MAX_DISPLAY_NAME_LENGTH, data: { default: @account.username } }, hint: false
= f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false = f.input :note, wrapper: :with_label, input_html: { maxlength: Account::MAX_NOTE_LENGTH }, hint: false
.fields-row .fields-row

View File

@ -15,12 +15,12 @@
= account_action_button(status.account) = account_action_button(status.account)
.status__content.emojify< .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text? - if status.spoiler_text?
%p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp; %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more') %button.status__content__spoiler-link= t('statuses.show_more')
.e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" } .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
- if status.preloadable_poll - if status.preloadable_poll
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do

View File

@ -10,13 +10,15 @@
- percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0 - percent = total_votes_count > 0 ? 100 * option.votes_count / total_votes_count : 0
%label.poll__option>< %label.poll__option><
%span.poll__number>< %span.poll__number><
- if own_votes.include?(index)
%i.poll__voted__mark.fa.fa-check
= "#{percent.round}%" = "#{percent.round}%"
%span.poll__option__text %span.poll__option__text
= Formatter.instance.format_poll_option(status, option, autoplay: autoplay) = Formatter.instance.format_poll_option(status, option, autoplay: autoplay)
- if own_votes.include?(index)
%span.poll__voted
%i.poll__voted__mark.fa.fa-check
%span.poll__chart{ style: "width: #{percent}%" } %progress{ max: 100, value: percent < 1 ? 1 : percent, 'aria-hidden': 'true' }
%span.poll__chart
- else - else
%label.poll__option>< %label.poll__option><
%span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}>< %span.poll__input{ class: poll.multiple? ? 'checkbox' : nil}><

View File

@ -19,12 +19,12 @@
%span.display-name__account %span.display-name__account
= acct(status.account) = acct(status.account)
= fa_icon('lock') if status.account.locked? = fa_icon('lock') if status.account.locked?
.status__content.emojify< .status__content.emojify{ :data => ({ spoiler: current_account&.user&.setting_expand_spoilers ? 'expanded' : 'folded' } if status.spoiler_text?) }<
- if status.spoiler_text? - if status.spoiler_text?
%p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }< %p<
%span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp; %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)}&nbsp;
%button.status__content__spoiler-link= t('statuses.show_more') %button.status__content__spoiler-link= t('statuses.show_more')
.e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }< .e-content{ dir: rtl_status?(status) ? 'rtl' : 'ltr' }<
= Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay) = Formatter.instance.format(status, custom_emojify: true, autoplay: autoplay)
- if status.preloadable_poll - if status.preloadable_poll
= react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do = react_component :poll, disabled: true, poll: ActiveModelSerializers::SerializableResource.new(status.preloadable_poll, serializer: REST::PollSerializer, scope: current_user, scope_name: :current_user).as_json do
@ -51,18 +51,18 @@
.status__action-bar .status__action-bar
.status__action-bar__counter .status__action-bar__counter
= link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = link_to remote_interaction_path(status, type: :reply), class: 'status__action-bar-button icon-button modal-button' do
- if status.in_reply_to_id.nil? - if status.in_reply_to_id.nil?
= fa_icon 'reply fw' = fa_icon 'reply fw'
- else - else
= fa_icon 'reply-all fw' = fa_icon 'reply-all fw'
.status__action-bar__counter__label= obscured_counter status.replies_count .status__action-bar__counter__label= obscured_counter status.replies_count
= link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = link_to remote_interaction_path(status, type: :reblog), class: 'status__action-bar-button icon-button modal-button' do
- if status.distributable? - if status.distributable?
= fa_icon 'retweet fw' = fa_icon 'retweet fw'
- elsif status.private_visibility? || status.limited_visibility? - elsif status.private_visibility? || status.limited_visibility?
= fa_icon 'lock fw' = fa_icon 'lock fw'
- else - else
= fa_icon 'envelope fw' = fa_icon 'envelope fw'
= link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button', style: 'font-size: 18px; width: 23.1429px; height: 23.1429px; line-height: 23.15px;' do = link_to remote_interaction_path(status, type: :favourite), class: 'status__action-bar-button icon-button modal-button' do
= fa_icon 'star fw' = fa_icon 'star fw'

View File

@ -10,9 +10,25 @@ Paperclip.interpolates :filename do |attachment, style|
end end
end end
Paperclip.interpolates :prefix_path do |attachment, style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache' + File::SEPARATOR
else
''
end
end
Paperclip.interpolates :prefix_url do |attachment, style|
if attachment.storage_schema_version >= 1 && attachment.instance.respond_to?(:local?) && !attachment.instance.local?
'cache/'
else
''
end
end
Paperclip::Attachment.default_options.merge!( Paperclip::Attachment.default_options.merge!(
use_timestamp: false, use_timestamp: false,
path: ':class/:attachment/:id_partition/:style/:filename', path: ':prefix_url:class/:attachment/:id_partition/:style/:filename',
storage: :fog storage: :fog
) )
@ -91,7 +107,7 @@ else
Paperclip::Attachment.default_options.merge!( Paperclip::Attachment.default_options.merge!(
storage: :filesystem, storage: :filesystem,
use_timestamp: true, use_timestamp: true,
path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':class', ':attachment', ':id_partition', ':style', ':filename'), path: File.join(ENV.fetch('PAPERCLIP_ROOT_PATH', File.join(':rails_root', 'public', 'system')), ':prefix_path:class', ':attachment', ':id_partition', ':style', ':filename'),
url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:class/:attachment/:id_partition/:style/:filename', url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:prefix_url:class/:attachment/:id_partition/:style/:filename',
) )
end end

View File

@ -0,0 +1,9 @@
class AddStorageSchemaVersion < ActiveRecord::Migration[5.2]
def change
add_column :preview_cards, :image_storage_schema_version, :integer
add_column :accounts, :avatar_storage_schema_version, :integer
add_column :accounts, :header_storage_schema_version, :integer
add_column :media_attachments, :file_storage_schema_version, :integer
add_column :custom_emojis, :image_storage_schema_version, :integer
end
end

View File

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_04_07_202420) do ActiveRecord::Schema.define(version: 2020_04_17_125749) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -172,6 +172,8 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do
t.datetime "suspended_at" t.datetime "suspended_at"
t.integer "trust_level" t.integer "trust_level"
t.boolean "hide_collections" t.boolean "hide_collections"
t.integer "avatar_storage_schema_version"
t.integer "header_storage_schema_version"
t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin t.index "(((setweight(to_tsvector('simple'::regconfig, (display_name)::text), 'A'::\"char\") || setweight(to_tsvector('simple'::regconfig, (username)::text), 'B'::\"char\")) || setweight(to_tsvector('simple'::regconfig, (COALESCE(domain, ''::character varying))::text), 'C'::\"char\")))", name: "search_index", using: :gin
t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true t.index "lower((username)::text), lower((domain)::text)", name: "index_accounts_on_username_and_domain_lower", unique: true
t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id" t.index ["moved_to_account_id"], name: "index_accounts_on_moved_to_account_id"
@ -299,6 +301,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do
t.string "image_remote_url" t.string "image_remote_url"
t.boolean "visible_in_picker", default: true, null: false t.boolean "visible_in_picker", default: true, null: false
t.bigint "category_id" t.bigint "category_id"
t.integer "image_storage_schema_version"
t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true t.index ["shortcode", "domain"], name: "index_custom_emojis_on_shortcode_and_domain", unique: true
end end
@ -465,6 +468,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do
t.bigint "scheduled_status_id" t.bigint "scheduled_status_id"
t.string "blurhash" t.string "blurhash"
t.integer "processing" t.integer "processing"
t.integer "file_storage_schema_version"
t.index ["account_id"], name: "index_media_attachments_on_account_id" t.index ["account_id"], name: "index_media_attachments_on_account_id"
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id" t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id"
t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true t.index ["shortcode"], name: "index_media_attachments_on_shortcode", unique: true
@ -605,6 +609,7 @@ ActiveRecord::Schema.define(version: 2020_04_07_202420) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "embed_url", default: "", null: false t.string "embed_url", default: "", null: false
t.integer "image_storage_schema_version"
t.index ["url"], name: "index_preview_cards_on_url", unique: true t.index ["url"], name: "index_preview_cards_on_url", unique: true
end end

12
ide-helper.js Normal file
View File

@ -0,0 +1,12 @@
/* global path */
/*
Preferences | Languages & Frameworks | JavaScript | Webpack | webpack configuration file
jetbrains://WebStorm/settings?name=Languages+%26+Frameworks--JavaScript--Webpack
*/
module.exports = {
resolve: {
alias: {
'mastodon': path.resolve(__dirname, 'app/javascript/mastodon'),
},
},
};

View File

@ -11,6 +11,7 @@ require_relative 'mastodon/statuses_cli'
require_relative 'mastodon/domains_cli' require_relative 'mastodon/domains_cli'
require_relative 'mastodon/preview_cards_cli' require_relative 'mastodon/preview_cards_cli'
require_relative 'mastodon/cache_cli' require_relative 'mastodon/cache_cli'
require_relative 'mastodon/upgrade_cli'
require_relative 'mastodon/version' require_relative 'mastodon/version'
module Mastodon module Mastodon
@ -49,6 +50,9 @@ module Mastodon
desc 'cache SUBCOMMAND ...ARGS', 'Manage cache' desc 'cache SUBCOMMAND ...ARGS', 'Manage cache'
subcommand 'cache', Mastodon::CacheCLI subcommand 'cache', Mastodon::CacheCLI
desc 'upgrade SUBCOMMAND ...ARGS', 'Various version upgrade utilities'
subcommand 'upgrade', Mastodon::UpgradeCLI
option :dry_run, type: :boolean option :dry_run, type: :boolean
desc 'self-destruct', 'Erase the server from the federation' desc 'self-destruct', 'Erase the server from the federation'
long_desc <<~LONG_DESC long_desc <<~LONG_DESC

View File

@ -10,6 +10,10 @@ Paperclip.options[:log] = false
module Mastodon module Mastodon
module CLIHelper module CLIHelper
def dry_run?
options[:dry_run]
end
def create_progress_bar(total = nil) def create_progress_bar(total = nil)
ProgressBar.create(total: total, format: '%c/%u |%b%i| %e') ProgressBar.create(total: total, format: '%c/%u |%b%i| %e')
end end

View File

@ -23,7 +23,7 @@ module Mastodon
Existing emoji will be skipped unless the --overwrite option Existing emoji will be skipped unless the --overwrite option
is provided, in which case they will be overwritten. is provided, in which case they will be overwritten.
You can specifiy a --category under which the emojis will be You can specify a --category under which the emojis will be
grouped together. grouped together.
With the --prefix option, a prefix can be added to all With the --prefix option, a prefix can be added to all
@ -72,6 +72,48 @@ module Mastodon
say("Imported #{imported}, skipped #{skipped}, failed to import #{failed}", color(imported, skipped, failed)) say("Imported #{imported}, skipped #{skipped}, failed to import #{failed}", color(imported, skipped, failed))
end end
option :category
option :overwrite, type: :boolean
desc 'export PATH', 'Export emoji to a TAR GZIP archive at PATH'
long_desc <<-LONG_DESC
Exports custom emoji to 'export.tar.gz' at PATH.
The --category option dumps only the specified category.
If this option is not specified, all emoji will be exported.
The --overwrite option will overwrite an existing archive.
LONG_DESC
def export(path)
exported = 0
category = CustomEmojiCategory.find_by(name: options[:category])
export_file_name = File.join(path, 'export.tar.gz')
if File.file?(export_file_name) && !options[:overwrite]
say("Archive already exists! Use '--overwrite' to overwrite it!")
exit 1
end
if category.nil? && options[:category]
say("Unable to find category '#{options[:category]}'!")
exit 1
end
File.open(export_file_name, 'wb') do |file|
Zlib::GzipWriter.wrap(file) do |gzip|
Gem::Package::TarWriter.new(gzip) do |tar|
scope = !options[:category] || category.nil? ? CustomEmoji.local : category.emojis
scope.find_each do |emoji|
say("Adding '#{emoji.shortcode}'...")
tar.add_file_simple(emoji.shortcode + File.extname(emoji.image_file_name), 0o644, emoji.image_file_size) do |io|
io.write Paperclip.io_adapters.for(emoji.image).read
exported += 1
end
end
end
end
end
say("Exported #{exported}")
end
option :remote_only, type: :boolean option :remote_only, type: :boolean
desc 'purge', 'Remove all custom emoji' desc 'purge', 'Remove all custom emoji'
long_desc <<-LONG_DESC long_desc <<-LONG_DESC

View File

@ -86,6 +86,8 @@ module Mastodon
objects.each do |object| objects.each do |object|
path_segments = object.key.split('/') path_segments = object.key.split('/')
path_segments.delete('cache')
model_name = path_segments.first.classify model_name = path_segments.first.classify
attachment_name = path_segments[1].singularize attachment_name = path_segments[1].singularize
record_id = path_segments[2..-2].join.to_i record_id = path_segments[2..-2].join.to_i
@ -121,7 +123,10 @@ module Mastodon
next if File.directory?(path) next if File.directory?(path)
key = path.gsub("#{root_path}#{File::SEPARATOR}", '') key = path.gsub("#{root_path}#{File::SEPARATOR}", '')
path_segments = key.split(File::SEPARATOR) path_segments = key.split(File::SEPARATOR)
path_segments.delete('cache')
model_name = path_segments.first.classify model_name = path_segments.first.classify
record_id = path_segments[2..-2].join.to_i record_id = path_segments[2..-2].join.to_i
attachment_name = path_segments[1].singularize attachment_name = path_segments[1].singularize
@ -230,7 +235,10 @@ module Mastodon
desc 'lookup URL', 'Lookup where media is displayed by passing a media URL' desc 'lookup URL', 'Lookup where media is displayed by passing a media URL'
def lookup(url) def lookup(url)
path = Addressable::URI.parse(url).path path = Addressable::URI.parse(url).path
path_segments = path.split('/')[2..-1] path_segments = path.split('/')[2..-1]
path_segments.delete('cache')
model_name = path_segments.first.classify model_name = path_segments.first.classify
record_id = path_segments[2..-2].join.to_i record_id = path_segments[2..-2].join.to_i
@ -277,6 +285,8 @@ module Mastodon
objects.map do |object| objects.map do |object|
segments = object.key.split('/') segments = object.key.split('/')
segments.delete('cache')
model_name = segments.first.classify model_name = segments.first.classify
record_id = segments[2..-2].join.to_i record_id = segments[2..-2].join.to_i

148
lib/mastodon/upgrade_cli.rb Normal file
View File

@ -0,0 +1,148 @@
# frozen_string_literal: true
require_relative '../../config/boot'
require_relative '../../config/environment'
require_relative 'cli_helper'
module Mastodon
class UpgradeCLI < Thor
include CLIHelper
def self.exit_on_failure?
true
end
CURRENT_STORAGE_SCHEMA_VERSION = 1
option :dry_run, type: :boolean, default: false
option :verbose, type: :boolean, default: false, aliases: [:v]
desc 'storage-schema', 'Upgrade storage schema of various file attachments to the latest version'
long_desc <<~LONG_DESC
Iterates over every file attachment of every record and, if its storage schema is outdated, performs the
necessary upgrade to the latest one. In practice this means e.g. moving files to different directories.
Will most likely take a long time.
LONG_DESC
def storage_schema
progress = create_progress_bar(nil)
dry_run = dry_run? ? ' (DRY RUN)' : ''
records = 0
klasses = [
Account,
CustomEmoji,
MediaAttachment,
PreviewCard,
]
klasses.each do |klass|
attachment_names = klass.attachment_definitions.keys
klass.find_each do |record|
attachment_names.each do |attachment_name|
attachment = record.public_send(attachment_name)
next if attachment.blank? || attachment.storage_schema_version >= CURRENT_STORAGE_SCHEMA_VERSION
attachment.styles.each_key do |style|
case Paperclip::Attachment.default_options[:storage]
when :s3
upgrade_storage_s3(progress, attachment, style)
when :fog
upgrade_storage_fog(progress, attachment, style)
when :filesystem
upgrade_storage_filesystem(progress, attachment, style)
end
progress.increment
end
attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
end
if record.changed?
record.save unless dry_run?
records += 1
end
end
end
progress.total = progress.progress
progress.finish
say("Upgraded storage schema of #{records} records#{dry_run}", :green, true)
end
private
def upgrade_storage_s3(progress, attachment, style)
previous_storage_schema_version = attachment.storage_schema_version
object = attachment.s3_object(style)
attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
upgraded_path = attachment.path(style)
if upgraded_path != object.key && object.exists?
progress.log("Moving #{object.key} to #{upgraded_path}") if options[:verbose]
begin
object.move_to(upgraded_path) unless dry_run?
rescue => e
progress.log(pastel.red("Error processing #{object.key}: #{e}"))
end
end
# Because we move files style-by-style, it's important to restore
# previous version at the end. The upgrade will be recorded after
# all styles are updated
attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
end
def upgrade_storage_fog(_progress, _attachment, _style)
say('The fog storage driver is not supported for this operation at this time', :red)
exit(1)
end
def upgrade_storage_filesystem(progress, attachment, style)
previous_storage_schema_version = attachment.storage_schema_version
previous_path = attachment.path(style)
attachment.instance_write(:storage_schema_version, CURRENT_STORAGE_SCHEMA_VERSION)
upgraded_path = attachment.path(style)
if upgraded_path != previous_path && File.exist?(previous_path)
progress.log("Moving #{previous_path} to #{upgraded_path}") if options[:verbose]
begin
unless dry_run?
FileUtils.mkdir_p(File.dirname(upgraded_path))
FileUtils.mv(previous_path, upgraded_path)
begin
FileUtils.rmdir(previous_path, parents: true)
rescue Errno::ENOTEMPTY
# OK
end
end
rescue => e
progress.log(pastel.red("Error processing #{previous_path}: #{e}"))
unless dry_run?
begin
FileUtils.rmdir(upgraded_path, parents: true)
rescue Errno::ENOTEMPTY
# OK
end
end
end
end
# Because we move files style-by-style, it's important to restore
# previous version at the end. The upgrade will be recorded after
# all styles are updated
attachment.instance_write(:storage_schema_version, previous_storage_schema_version)
end
end
end

View File

@ -14,6 +14,15 @@ module Paperclip
end end
end end
def storage_schema_version
instance_read(:storage_schema_version) || 0
end
def assign_attributes
super
instance_write(:storage_schema_version, 1)
end
def variant?(other_filename) def variant?(other_filename)
return true if original_filename == other_filename return true if original_filename == other_filename
return false if original_filename.nil? return false if original_filename.nil?

View File

@ -3,21 +3,133 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::CollectionsController, type: :controller do RSpec.describe ActivityPub::CollectionsController, type: :controller do
describe 'POST #show' do let!(:account) { Fabricate(:account) }
let(:account) { Fabricate(:account) } let(:remote_account) { nil }
context 'id is "featured"' do before do
it 'returns 200 with "application/activity+json"' do allow(controller).to receive(:signed_request_account).and_return(remote_account)
post :show, params: { id: 'featured', account_username: account.username }
Fabricate(:status_pin, account: account)
Fabricate(:status_pin, account: account)
Fabricate(:status, account: account, visibility: :private)
end
describe 'GET #show' do
context 'when id is "featured"' do
context 'without signature' do
let(:remote_account) { nil }
before do
get :show, params: { id: 'featured', account_username: account.username }
end
it 'returns http success' do
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json' expect(response.content_type).to eq 'application/activity+json'
end end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end end
context 'id is not "featured"' do it 'returns orderedItems with pinned statuses' do
it 'returns 404' do json = body_as_json
post :show, params: { id: 'hoge', account_username: account.username } expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 2
end
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
context do
before do
get :show, params: { id: 'featured', account_username: account.username }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'returns orderedItems with pinned statuses' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 2
end
end
context 'in authorized fetch mode' do
before do
allow(controller).to receive(:authorized_fetch_mode?).and_return(true)
end
context 'when signed request account is blocked' do
before do
account.block!(remote_account)
get :show, params: { id: 'featured', account_username: account.username }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
end
context 'when signed request account is domain blocked' do
before do
account.block_domain!(remote_account.domain)
get :show, params: { id: 'featured', account_username: account.username }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
end
end
end
end
context 'when id is not "featured"' do
it 'returns http not found' do
get :show, params: { id: 'hoge', account_username: account.username }
expect(response).to have_http_status(404) expect(response).to have_http_status(404)
end end
end end

View File

@ -3,25 +3,31 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe ActivityPub::InboxesController, type: :controller do RSpec.describe ActivityPub::InboxesController, type: :controller do
describe 'POST #create' do let(:remote_account) { nil }
context 'with signed_request_account' do
it 'returns 202' do before do
allow(controller).to receive(:signed_request_account) do allow(controller).to receive(:signed_request_account).and_return(remote_account)
Fabricate(:account)
end end
describe 'POST #create' do
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com', protocol: :activitypub) }
before do
post :create, body: '{}' post :create, body: '{}'
end
it 'returns http accepted' do
expect(response).to have_http_status(202) expect(response).to have_http_status(202)
end end
end end
context 'without signed_request_account' do context 'without signature' do
it 'returns 401' do before do
allow(controller).to receive(:signed_request_account) do post :create, body: '{}'
false
end end
post :create, body: '{}' it 'returns http not authorized' do
expect(response).to have_http_status(401) expect(response).to have_http_status(401)
end end
end end

View File

@ -4,12 +4,77 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
let!(:account) { Fabricate(:account) } let!(:account) { Fabricate(:account) }
before do before do
Fabricate(:status, account: account) Fabricate(:status, account: account, visibility: :public)
Fabricate(:status, account: account, visibility: :unlisted)
Fabricate(:status, account: account, visibility: :private)
Fabricate(:status, account: account, visibility: :direct)
Fabricate(:status, account: account, visibility: :limited)
end
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end end
describe 'GET #show' do describe 'GET #show' do
context 'without signature' do
let(:remote_account) { nil }
before do before do
get :show, params: { account_username: account.username } get :show, params: { account_username: account.username, page: page }
end
context 'with page not requested' do
let(:page) { nil }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns totalItems' do
json = body_as_json
expect(json[:totalItems]).to eq 4
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
end
context 'with page requested' do
let(:page) { 'true' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns orderedItems with public or unlisted statuses' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 2
expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
end
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:page) { 'true' }
context 'when signed request account does not follow account' do
before do
get :show, params: { account_username: account.username, page: page }
end end
it 'returns http success' do it 'returns http success' do
@ -19,5 +84,94 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
it 'returns application/activity+json' do it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json' expect(response.content_type).to eq 'application/activity+json'
end end
it 'returns orderedItems with public or unlisted statuses' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 2
expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
context 'when signed request account follows account' do
before do
remote_account.follow!(account)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns orderedItems with private statuses' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 3
expect(json[:orderedItems].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:to].include?(account_followers_url(account, ActionMailer::Base.default_url_options)) }).to be true
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
context 'when signed request account is blocked' do
before do
account.block!(remote_account)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
context 'when signed request account is domain blocked' do
before do
account.block_domain!(remote_account.domain)
get :show, params: { account_username: account.username, page: page }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns empty orderedItems' do
json = body_as_json
expect(json[:orderedItems]).to be_an Array
expect(json[:orderedItems].size).to eq 0
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to eq 'max-age=0, private'
end
end
end
end end
end end

View File

@ -0,0 +1,196 @@
# frozen_string_literal: true
require 'rails_helper'
RSpec.describe ActivityPub::RepliesController, type: :controller do
let(:status) { Fabricate(:status, visibility: parent_visibility) }
let(:remote_account) { nil }
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
Fabricate(:status, thread: status, visibility: :public)
Fabricate(:status, thread: status, visibility: :public)
Fabricate(:status, thread: status, visibility: :private)
Fabricate(:status, account: status.account, thread: status, visibility: :public)
Fabricate(:status, account: status.account, thread: status, visibility: :private)
end
describe 'GET #index' do
context 'with no signature' do
before do
get :index, params: { account_username: status.account.username, status_id: status.id }
end
context 'when status is public' do
let(:parent_visibility) { :public }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'returns items with account\'s own replies' do
json = body_as_json
expect(json[:first]).to be_a Hash
expect(json[:first][:items]).to be_an Array
expect(json[:first][:items].size).to eq 1
expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
end
context 'when status is private' do
let(:parent_visibility) { :private }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is direct' do
let(:parent_visibility) { :direct }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
let(:only_other_accounts) { nil }
context do
before do
get :index, params: { account_username: status.account.username, status_id: status.id, only_other_accounts: only_other_accounts }
end
context 'when status is public' do
let(:parent_visibility) { :public }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns application/activity+json' do
expect(response.content_type).to eq 'application/activity+json'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
context 'without only_other_accounts' do
it 'returns items with account\'s own replies' do
json = body_as_json
expect(json[:first]).to be_a Hash
expect(json[:first][:items]).to be_an Array
expect(json[:first][:items].size).to eq 1
expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
end
context 'with only_other_accounts' do
let(:only_other_accounts) { 'true' }
it 'returns items with other public or unlisted replies' do
json = body_as_json
expect(json[:first]).to be_a Hash
expect(json[:first][:items]).to be_an Array
expect(json[:first][:items].size).to eq 2
expect(json[:first][:items].all? { |item| item[:to].include?(ActivityPub::TagManager::COLLECTIONS[:public]) || item[:cc].include?(ActivityPub::TagManager::COLLECTIONS[:public]) }).to be true
end
end
end
context 'when status is private' do
let(:parent_visibility) { :private }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is direct' do
let(:parent_visibility) { :direct }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
context 'when signed request account is blocked' do
before do
status.account.block!(remote_account)
get :index, params: { account_username: status.account.username, status_id: status.id }
end
context 'when status is public' do
let(:parent_visibility) { :public }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is private' do
let(:parent_visibility) { :private }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is direct' do
let(:parent_visibility) { :direct }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
context 'when signed request account is domain blocked' do
before do
status.account.block_domain!(remote_account.domain)
get :index, params: { account_username: status.account.username, status_id: status.id }
end
context 'when status is public' do
let(:parent_visibility) { :public }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is private' do
let(:parent_visibility) { :private }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is direct' do
let(:parent_visibility) { :direct }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
end
end

View File

@ -5,128 +5,821 @@ require 'rails_helper'
describe StatusesController do describe StatusesController do
render_views render_views
describe '#show' do describe 'GET #show' do
context 'account is suspended' do let(:account) { Fabricate(:account) }
it 'returns gone' do let(:status) { Fabricate(:status, account: account) }
account = Fabricate(:account, suspended: true)
status = Fabricate(:status, account: account)
context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }
before do
get :show, params: { account_username: account.username, id: status.id } get :show, params: { account_username: account.username, id: status.id }
end
it 'returns http gone' do
expect(response).to have_http_status(410) expect(response).to have_http_status(410)
end end
end end
context 'status is not permitted' do context 'when status is a reblog' do
it 'raises ActiveRecord::RecordNotFound' do let(:original_account) { Fabricate(:account, domain: 'example.com') }
user = Fabricate(:user) let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') }
status = Fabricate(:status) let(:status) { Fabricate(:status, account: account, reblog: original_status) }
status.account.block!(user.account)
sign_in(user) before do
get :show, params: { account_username: status.account.username, id: status.id } get :show, params: { account_username: status.account.username, id: status.id }
expect(response).to have_http_status(404)
end
end end
context 'status is a reblog' do
it 'redirects to the original status' do it 'redirects to the original status' do
original_account = Fabricate(:account, domain: 'example.com')
original_status = Fabricate(:status, account: original_account, uri: 'tag:example.com,2017:foo', url: 'https://example.com/123')
status = Fabricate(:status, reblog: original_status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(response).to redirect_to(original_status.url) expect(response).to redirect_to(original_status.url)
end end
end end
context 'account is not suspended and status is permitted' do context 'when status is public' do
it 'assigns @account' do before do
status = Fabricate(:status) get :show, params: { account_username: status.account.username, id: status.id, format: format }
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:account)).to eq status.account
end end
it 'assigns @status' do context 'as HTML' do
status = Fabricate(:status) let(:format) { 'html' }
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:status)).to eq status
end
it 'assigns @ancestors for ancestors of the status if it is a reply' do it 'returns http success' do
ancestor = Fabricate(:status)
status = Fabricate(:status, in_reply_to_id: ancestor.id)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:ancestors)).to eq [ancestor]
end
it 'assigns @ancestors for [] if it is not a reply' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:ancestors)).to eq []
end
it 'assigns @descendant_threads for a thread with several statuses' do
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
grandchild = Fabricate(:status, in_reply_to_id: child.id)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchild.id]
end
it 'assigns @descendant_threads for several threads sharing the same descendant' do
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
grandchildren = 2.times.map { Fabricate(:status, in_reply_to_id: child.id) }
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).to eq [child.id, grandchildren[0].id]
expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).to eq [grandchildren[1].id]
end
it 'assigns @max_descendant_thread_id for the last thread if it is hitting the status limit' do
stub_const 'StatusControllerConcern::DESCENDANTS_LIMIT', 1
status = Fabricate(:status)
child = Fabricate(:status, in_reply_to_id: status.id)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:descendant_threads)).to eq []
expect(assigns(:max_descendant_thread_id)).to eq child.id
end
it 'assigns @descendant_threads for threads with :next_status key if they are hitting the depth limit' do
stub_const 'StatusControllerConcern::DESCENDANTS_DEPTH_LIMIT', 2
status = Fabricate(:status)
child0 = Fabricate(:status, in_reply_to_id: status.id)
child1 = Fabricate(:status, in_reply_to_id: child0.id)
child2 = Fabricate(:status, in_reply_to_id: child0.id)
get :show, params: { account_username: status.account.username, id: status.id }
expect(assigns(:descendant_threads)[0][:statuses].pluck(:id)).not_to include child1.id
expect(assigns(:descendant_threads)[1][:statuses].pluck(:id)).not_to include child2.id
expect(assigns(:descendant_threads)[0][:next_status].id).to eq child1.id
expect(assigns(:descendant_threads)[1][:next_status].id).to eq child2.id
end
it 'returns a success' do
status = Fabricate(:status)
get :show, params: { account_username: status.account.username, id: status.id }
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
it 'renders statuses/show' do it 'returns Link header' do
status = Fabricate(:status) expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
context 'when signed-in' do
let(:user) { Fabricate(:user) }
before do
sign_in(user)
end
context 'when account blocks user' do
before do
account.block!(user.account)
get :show, params: { account_username: status.account.username, id: status.id } get :show, params: { account_username: status.account.username, id: status.id }
expect(response).to render_template 'statuses/show' end
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is public' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }
context 'when user is authorized to see it' do
before do
user.account.follow!(account)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }
context 'when user is authorized to see it' do
before do
Fabricate(:mention, account: user.account, status: status)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
end
context 'with signature' do
let(:remote_account) { Fabricate(:account, domain: 'example.com') }
before do
allow(controller).to receive(:signed_request_account).and_return(remote_account)
end
context 'when account blocks account' do
before do
account.block!(remote_account)
get :show, params: { account_username: status.account.username, id: status.id }
end
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when account domain blocks account' do
before do
account.block_domain!(remote_account.domain)
get :show, params: { account_username: status.account.username, id: status.id }
end
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is public' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }
context 'when user is authorized to see it' do
before do
remote_account.follow!(account)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }
context 'when user is authorized to see it' do
before do
Fabricate(:mention, account: remote_account, status: status)
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns no Cache-Control header' do
expect(response.headers).to_not include 'Cache-Control'
end
it 'renders status' do
expect(response).to render_template(:show)
expect(response.body).to include status.text
end
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns private Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'private'
end
it 'returns Content-Type header' do
expect(response.headers['Content-Type']).to include 'application/activity+json'
end
it 'renders ActivityPub Note object' do
json = body_as_json
expect(json[:content]).to include status.text
end
end
end
context 'when user is not authorized to see it' do
before do
get :show, params: { account_username: status.account.username, id: status.id, format: format }
end
context 'as JSON' do
let(:format) { 'json' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'as HTML' do
let(:format) { 'html' }
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
end
end
end
end
describe 'GET #activity' do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }
context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }
before do
get :activity, params: { account_username: account.username, id: status.id }
end
it 'returns http gone' do
expect(response).to have_http_status(410)
end
end
context 'when status is public' do
pending
end
context 'when status is private' do
pending
end
context 'when status is direct' do
pending
end
context 'when signed-in' do
context 'when status is public' do
pending
end
context 'when status is private' do
context 'when user is authorized to see it' do
pending
end
context 'when user is not authorized to see it' do
pending
end
end
context 'when status is direct' do
context 'when user is authorized to see it' do
pending
end
context 'when user is not authorized to see it' do
pending
end
end
end
context 'with signature' do
context 'when status is public' do
pending
end
context 'when status is private' do
context 'when user is authorized to see it' do
pending
end
context 'when user is not authorized to see it' do
pending
end
end
context 'when status is direct' do
context 'when user is authorized to see it' do
pending
end
context 'when user is not authorized to see it' do
pending
end
end
end
end
describe 'GET #embed' do
let(:account) { Fabricate(:account) }
let(:status) { Fabricate(:status, account: account) }
context 'when account is suspended' do
let(:account) { Fabricate(:account, suspended: true) }
before do
get :embed, params: { account_username: account.username, id: status.id }
end
it 'returns http gone' do
expect(response).to have_http_status(410)
end
end
context 'when status is a reblog' do
let(:original_account) { Fabricate(:account, domain: 'example.com') }
let(:original_status) { Fabricate(:status, account: original_account, url: 'https://example.com/123') }
let(:status) { Fabricate(:status, account: account, reblog: original_status) }
before do
get :embed, params: { account_username: status.account.username, id: status.id }
end
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is public' do
before do
get :embed, params: { account_username: status.account.username, id: status.id }
end
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'returns Link header' do
expect(response.headers['Link'].to_s).to include 'activity+json'
end
it 'returns Vary header' do
expect(response.headers['Vary']).to eq 'Accept'
end
it 'returns public Cache-Control header' do
expect(response.headers['Cache-Control']).to include 'public'
end
it 'renders status' do
expect(response).to render_template(:embed)
expect(response.body).to include status.text
end
end
context 'when status is private' do
let(:status) { Fabricate(:status, account: account, visibility: :private) }
before do
get :embed, params: { account_username: status.account.username, id: status.id }
end
it 'returns http not found' do
expect(response).to have_http_status(404)
end
end
context 'when status is direct' do
let(:status) { Fabricate(:status, account: account, visibility: :direct) }
before do
get :embed, params: { account_username: status.account.username, id: status.id }
end
it 'returns http not found' do
expect(response).to have_http_status(404)
end end
end end
end end

View File

@ -1,4 +1,4 @@
Fabricator(:status_pin) do Fabricator(:status_pin) do
account account
status status { |attrs| Fabricate(:status, account: attrs[:account], visibility: :public) }
end end

View File

@ -96,20 +96,16 @@ RSpec.describe Status, type: :model do
context 'unless destroyed?' do context 'unless destroyed?' do
context 'if reblog?' do context 'if reblog?' do
it 'returns "#{account.acct} shared #{reblog.account.acct}\'s: #{preview}"' do it 'returns "#{account.acct} shared a status by #{reblog.account.acct}"' do
reblog = subject.reblog = other reblog = subject.reblog = other
preview = subject.text.slice(0, 10).split("\n")[0] expect(subject.title).to eq "#{account.acct} shared a status by #{reblog.account.acct}"
expect(subject.title).to(
eq "#{account.acct} shared #{reblog.account.acct}'s: #{preview}"
)
end end
end end
context 'unless reblog?' do context 'unless reblog?' do
it 'returns "#{account.acct}: #{preview}"' do it 'returns "New status by #{account.acct}"' do
subject.reblog = nil subject.reblog = nil
preview = subject.text.slice(0, 20).split("\n")[0] expect(subject.title).to eq "New status by #{account.acct}"
expect(subject.title).to eq "#{account.acct}: #{preview}"
end end
end end
end end

View File

@ -21,7 +21,11 @@ RSpec.describe FetchResourceService, type: :service do
context 'when OpenSSL::SSL::SSLError is raised' do context 'when OpenSSL::SSL::SSLError is raised' do
before do before do
allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(OpenSSL::SSL::SSLError) request = double()
allow(Request).to receive(:new).and_return(request)
allow(request).to receive(:add_headers)
allow(request).to receive(:on_behalf_of)
allow(request).to receive(:perform).and_raise(OpenSSL::SSL::SSLError)
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }
@ -29,7 +33,11 @@ RSpec.describe FetchResourceService, type: :service do
context 'when HTTP::ConnectionError is raised' do context 'when HTTP::ConnectionError is raised' do
before do before do
allow(Request).to receive_message_chain(:new, :add_headers, :on_behalf_of, :perform).and_raise(HTTP::ConnectionError) request = double()
allow(Request).to receive(:new).and_return(request)
allow(request).to receive(:add_headers)
allow(request).to receive(:on_behalf_of)
allow(request).to receive(:perform).and_raise(HTTP::ConnectionError)
end end
it { is_expected.to be_nil } it { is_expected.to be_nil }

367
yarn.lock
View File

@ -1327,9 +1327,9 @@
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
"@types/q@^1.5.1": "@types/q@^1.5.1":
version "1.5.1" version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.1.tgz#48fd98c1561fe718b61733daed46ff115b496e18" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
integrity sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA== integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
"@types/react@16.4.6": "@types/react@16.4.6":
version "16.4.6" version "16.4.6"
@ -1632,7 +1632,7 @@ ajv@^4.7.0:
co "^4.6.0" co "^4.6.0"
json-stable-stringify "^1.0.1" json-stable-stringify "^1.0.1"
ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5, ajv@^6.9.1: ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.9.1:
version "6.10.2" version "6.10.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw== integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
@ -1652,6 +1652,16 @@ ajv@^6.12.0:
json-schema-traverse "^0.4.1" json-schema-traverse "^0.4.1"
uri-js "^4.2.2" uri-js "^4.2.2"
ajv@^6.5.5:
version "6.12.2"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.2.tgz#c629c5eced17baf314437918d2da88c99d5958cd"
integrity sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
alphanum-sort@^1.0.0: alphanum-sort@^1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
@ -1971,9 +1981,9 @@ aws-sign2@~0.7.0:
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.8.0: aws4@^1.8.0:
version "1.8.0" version "1.9.1"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.1.tgz#7e33d8f7d449b3f673cd72deb9abdc552dbe528e"
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== integrity sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==
axios@^0.18.0: axios@^0.18.0:
version "0.18.1" version "0.18.1"
@ -2249,9 +2259,9 @@ bindings@^1.5.0:
file-uri-to-path "1.0.0" file-uri-to-path "1.0.0"
bluebird@^3.5.5: bluebird@^3.5.5:
version "3.5.5" version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
blurhash@^1.1.3: blurhash@^1.1.3:
version "1.1.3" version "1.1.3"
@ -2461,11 +2471,6 @@ buffer@^4.3.0:
ieee754 "^1.1.4" ieee754 "^1.1.4"
isarray "^1.0.0" isarray "^1.0.0"
builtin-modules@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f"
integrity sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=
builtin-status-codes@^3.0.0: builtin-status-codes@^3.0.0:
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@ -2482,9 +2487,9 @@ bytes@3.1.0:
integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==
cacache@^12.0.2, cacache@^12.0.3: cacache@^12.0.2, cacache@^12.0.3:
version "12.0.3" version "12.0.4"
resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.3.tgz#be99abba4e1bf5df461cd5a2c1071fc432573390" resolved "https://registry.yarnpkg.com/cacache/-/cacache-12.0.4.tgz#668bcbd105aeb5f1d92fe25570ec9525c8faa40c"
integrity sha512-kqdmfXEGFepesTuROHMs3MpFLWrPkSSpRqOw80RCflZXy/khxaArvFrQ7uJxSUduzAufc6G0g1VUCOZXxWavPw== integrity sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==
dependencies: dependencies:
bluebird "^3.5.5" bluebird "^3.5.5"
chownr "^1.1.1" chownr "^1.1.1"
@ -2703,16 +2708,11 @@ chokidar@^2.1.8:
optionalDependencies: optionalDependencies:
fsevents "^1.2.7" fsevents "^1.2.7"
chownr@^1.1.1: chownr@^1.1.1, chownr@^1.1.2:
version "1.1.4" version "1.1.4"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b"
integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg== integrity sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==
chownr@^1.1.2:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142"
integrity sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==
chrome-trace-event@^1.0.2: chrome-trace-event@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4"
@ -2818,7 +2818,7 @@ co@^4.6.0:
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
coa@~2.0.1: coa@^2.0.2:
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3"
integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA== integrity sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==
@ -2880,15 +2880,10 @@ color@^3.0.0:
color-convert "^1.9.1" color-convert "^1.9.1"
color-string "^1.5.2" color-string "^1.5.2"
colors@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63"
integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM=
combined-stream@^1.0.6, combined-stream@~1.0.6: combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.7" version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
@ -3052,9 +3047,9 @@ core-js-compat@^3.6.2:
semver "7.0.0" semver "7.0.0"
core-js-pure@^3.0.0: core-js-pure@^3.0.0:
version "3.6.4" version "3.6.5"
resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.4.tgz#4bf1ba866e25814f149d4e9aaa08c36173506e3a" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813"
integrity sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw== integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==
core-js@^2.4.0: core-js@^2.4.0:
version "2.6.1" version "2.6.1"
@ -3235,18 +3230,18 @@ css-loader@^3.4.2:
postcss-value-parser "^4.0.2" postcss-value-parser "^4.0.2"
schema-utils "^2.6.0" schema-utils "^2.6.0"
css-select-base-adapter@~0.1.0: css-select-base-adapter@^0.1.1:
version "0.1.1" version "0.1.1"
resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7"
integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w== integrity sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==
css-select@^2.0.0: css-select@^2.0.0:
version "2.0.2" version "2.1.0"
resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.0.2.tgz#ab4386cec9e1f668855564b17c3733b43b2a5ede" resolved "https://registry.yarnpkg.com/css-select/-/css-select-2.1.0.tgz#6a34653356635934a81baca68d0255432105dbef"
integrity sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ== integrity sha512-Dqk7LQKpwLoH3VovzZnkzegqNSuAziQyNZUcrdDM401iY+R5NkGBXGmtO05/yaXQziALuPogeG0b7UAgjnTJTQ==
dependencies: dependencies:
boolbase "^1.0.0" boolbase "^1.0.0"
css-what "^2.1.2" css-what "^3.2.1"
domutils "^1.7.0" domutils "^1.7.0"
nth-check "^1.0.2" nth-check "^1.0.2"
@ -3265,37 +3260,37 @@ css-system-font-keywords@^1.0.0:
resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed" resolved "https://registry.yarnpkg.com/css-system-font-keywords/-/css-system-font-keywords-1.0.0.tgz#85c6f086aba4eb32c571a3086affc434b84823ed"
integrity sha1-hcbwhquk6zLFcaMIav/ENLhII+0= integrity sha1-hcbwhquk6zLFcaMIav/ENLhII+0=
css-tree@1.0.0-alpha.28: css-tree@1.0.0-alpha.37:
version "1.0.0-alpha.28" version "1.0.0-alpha.37"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.28.tgz#8e8968190d886c9477bc8d61e96f61af3f7ffa7f" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
integrity sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w== integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg==
dependencies: dependencies:
mdn-data "~1.1.0" mdn-data "2.0.4"
source-map "^0.5.3" source-map "^0.6.1"
css-tree@1.0.0-alpha.29: css-tree@1.0.0-alpha.39:
version "1.0.0-alpha.29" version "1.0.0-alpha.39"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.29.tgz#3fa9d4ef3142cbd1c301e7664c1f352bd82f5a39" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb"
integrity sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg== integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA==
dependencies: dependencies:
mdn-data "~1.1.0" mdn-data "2.0.6"
source-map "^0.5.3" source-map "^0.6.1"
css-unit-converter@^1.1.1: css-unit-converter@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996" resolved "https://registry.yarnpkg.com/css-unit-converter/-/css-unit-converter-1.1.1.tgz#d9b9281adcfd8ced935bdbaba83786897f64e996"
integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY= integrity sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=
css-url-regex@^1.1.0: css-what@2.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/css-url-regex/-/css-url-regex-1.1.0.tgz#83834230cc9f74c457de59eebd1543feeb83b7ec"
integrity sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=
css-what@2.1, css-what@^2.1.2:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2"
integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==
css-what@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.2.1.tgz#f4a8f12421064621b456755e34a03a2c22df5da1"
integrity sha512-WwOrosiQTvyms+Ti5ZC5vGEK0Vod3FTt1ca+payZqvKuGJF+dq7bG63DstxtN0dpm6FxY27a/zS3Wten+gEtGw==
cssesc@^2.0.0: cssesc@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
@ -3374,12 +3369,12 @@ cssnano@^4.1.10:
is-resolvable "^1.0.0" is-resolvable "^1.0.0"
postcss "^7.0.0" postcss "^7.0.0"
csso@^3.5.0: csso@^4.0.2:
version "3.5.1" version "4.0.3"
resolved "https://registry.yarnpkg.com/csso/-/csso-3.5.1.tgz#7b9eb8be61628973c1b261e169d2f024008e758b" resolved "https://registry.yarnpkg.com/csso/-/csso-4.0.3.tgz#0d9985dc852c7cc2b2cacfbbe1079014d1a8e903"
integrity sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg== integrity sha512-NL3spysxUkcrOgnpsT4Xdl2aiEiBG6bXswAABQVHcMrfjjBisFOKwLDOmf4wf32aPdcJws1zds2B0Rg+jqMyHQ==
dependencies: dependencies:
css-tree "1.0.0-alpha.29" css-tree "1.0.0-alpha.39"
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.8" version "0.3.8"
@ -3961,7 +3956,7 @@ error-stack-parser@^2.0.6:
dependencies: dependencies:
stackframe "^1.1.1" stackframe "^1.1.1"
es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.5, es-abstract@^1.5.1: es-abstract@^1.13.0, es-abstract@^1.17.0, es-abstract@^1.17.0-next.0, es-abstract@^1.17.0-next.1, es-abstract@^1.17.2, es-abstract@^1.17.5:
version "1.17.5" version "1.17.5"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9"
integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==
@ -4524,9 +4519,9 @@ fast-deep-equal@^3.1.1:
integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==
fast-json-stable-stringify@^2.0.0: fast-json-stable-stringify@^2.0.0:
version "2.0.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@~2.0.6: fast-levenshtein@~2.0.6:
version "2.0.6" version "2.0.6"
@ -4560,9 +4555,9 @@ fb-watchman@^2.0.0:
bser "^2.0.0" bser "^2.0.0"
figgy-pudding@^3.5.1: figgy-pudding@^3.5.1:
version "3.5.1" version "3.5.2"
resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e"
integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==
figures@^1.3.5: figures@^1.3.5:
version "1.7.0" version "1.7.0"
@ -4722,9 +4717,9 @@ flat-cache@^2.0.1:
write "1.0.3" write "1.0.3"
flatted@^2.0.0: flatted@^2.0.0:
version "2.0.0" version "2.0.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916" resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138"
integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg== integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==
flush-write-stream@^1.0.0: flush-write-stream@^1.0.0:
version "1.1.1" version "1.1.1"
@ -4890,9 +4885,9 @@ functional-red-black-tree@^1.0.1:
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
functions-have-names@^1.2.0: functions-have-names@^1.2.0:
version "1.2.0" version "1.2.1"
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.0.tgz#83da7583e4ea0c9ac5ff530f73394b033e0bf77d" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91"
integrity sha512-zKXyzksTeaCSw5wIX79iCA40YAa6CJMJgNg9wdkU/ERBrIdPSimPICYiLp65lRbSBqtiHql/HZfS2DyI/AH6tQ== integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA==
gauge@~2.7.3: gauge@~2.7.3:
version "2.7.4" version "2.7.4"
@ -5119,7 +5114,7 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.1.0: har-validator@~5.1.3:
version "5.1.3" version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
@ -5275,9 +5270,9 @@ hoopy@^0.1.4:
integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ== integrity sha512-HRcs+2mr52W0K+x8RzcLzuPPmVIKMSv97RGHy0Ea9y/mpcaK+xTrjICA04KAHi4GRzxliNqNJEFYWHghy3rSfQ==
hosted-git-info@^2.1.4: hosted-git-info@^2.1.4:
version "2.7.1" version "2.8.8"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488"
integrity sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w== integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==
hpack.js@^2.1.6: hpack.js@^2.1.6:
version "2.1.6" version "2.1.6"
@ -5759,13 +5754,6 @@ is-buffer@^2.0.2:
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725"
integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==
is-builtin-module@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe"
integrity sha1-VAVy0096wxGfj3bDDLwbHgN6/74=
dependencies:
builtin-modules "^1.0.0"
is-callable@^1.1.4, is-callable@^1.1.5: is-callable@^1.1.4, is-callable@^1.1.5:
version "1.1.5" version "1.1.5"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
@ -6561,7 +6549,7 @@ js-string-escape@1.0.1:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.12.0, js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0: js-yaml@^3.13.1, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.9.0:
version "3.13.1" version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
@ -6928,9 +6916,9 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11,
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
loglevel@^1.6.6: loglevel@^1.6.6:
version "1.6.6" version "1.6.8"
resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.8.tgz#8a25fb75d092230ecd4457270d80b54e28011171"
integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ== integrity sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
@ -7013,10 +7001,15 @@ md5.js@^1.3.4:
inherits "^2.0.1" inherits "^2.0.1"
safe-buffer "^5.1.2" safe-buffer "^5.1.2"
mdn-data@~1.1.0: mdn-data@2.0.4:
version "1.1.4" version "2.0.4"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA== integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
mdn-data@2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978"
integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA==
media-typer@0.3.0: media-typer@0.3.0:
version "0.3.0" version "0.3.0"
@ -7100,34 +7093,22 @@ miller-rabin@^4.0.0:
bn.js "^4.0.0" bn.js "^4.0.0"
brorand "^1.0.1" brorand "^1.0.1"
mime-db@1.40.0: mime-db@1.44.0:
version "1.40.0" version "1.44.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA== integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
"mime-db@>= 1.40.0 < 2": "mime-db@>= 1.40.0 < 2":
version "1.42.0" version "1.42.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ== integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
mime-db@~1.37.0: mime-types@^2.1.12, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24:
version "1.37.0" version "2.1.27"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
mime-types@^2.1.12, mime-types@~2.1.19:
version "2.1.21"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==
dependencies: dependencies:
mime-db "~1.37.0" mime-db "1.44.0"
mime-types@~2.1.17, mime-types@~2.1.24:
version "2.1.24"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
dependencies:
mime-db "1.40.0"
mime@1.6.0: mime@1.6.0:
version "1.6.0" version "1.6.0"
@ -7253,14 +7234,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2" for-in "^1.0.2"
is-extendable "^1.0.1" is-extendable "^1.0.1"
mkdirp@^0.5, mkdirp@^0.5.3, mkdirp@~0.5.1: mkdirp@^0.5, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1:
version "0.5.4"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512"
integrity sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==
dependencies:
minimist "^1.2.5"
mkdirp@^0.5.0, mkdirp@^0.5.1:
version "0.5.5" version "0.5.5"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
@ -7272,10 +7246,10 @@ mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
moo@^0.4.3: moo@^0.5.0:
version "0.4.3" version "0.5.1"
resolved "https://registry.yarnpkg.com/moo/-/moo-0.4.3.tgz#3f847a26f31cf625a956a87f2b10fbc013bfd10e" resolved "https://registry.yarnpkg.com/moo/-/moo-0.5.1.tgz#7aae7f384b9b09f620b6abf6f74ebbcd1b65dbc4"
integrity sha512-gFD2xGCl8YFgGHsqJ9NKRVdwlioeW3mI1iqfLNYQOv0+6JRwG58Zk9DIGQgyIaffSYaO1xsKnMaYzzNr1KyIAw== integrity sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w==
mousetrap@^1.5.2: mousetrap@^1.5.2:
version "1.6.5" version "1.6.5"
@ -7360,12 +7334,12 @@ natural-compare@^1.4.0:
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
nearley@^2.7.10: nearley@^2.7.10:
version "2.16.0" version "2.19.2"
resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.16.0.tgz#77c297d041941d268290ec84b739d0ee297e83a7" resolved "https://registry.yarnpkg.com/nearley/-/nearley-2.19.2.tgz#40cafbf235121ae94b1aa1e585890d24fade182d"
integrity sha512-Tr9XD3Vt/EujXbZBv6UAHYoLUSMQAxSsTnm9K3koXzjzNWY195NqALeyrzLZBKzAkL3gl92BcSogqrHjD8QuUg== integrity sha512-h6lygT0BWAGErDvoE2LfI+tDeY2+UUrqG5dcBPdCmjnjud9z1wE0P7ljb85iNbE93YA+xJLpoSYGMuUqhnSSSA==
dependencies: dependencies:
commander "^2.19.0" commander "^2.19.0"
moo "^0.4.3" moo "^0.5.0"
railroad-diagrams "^1.0.0" railroad-diagrams "^1.0.0"
randexp "0.4.6" randexp "0.4.6"
semver "^5.4.1" semver "^5.4.1"
@ -7484,12 +7458,12 @@ nopt@^4.0.1:
osenv "^0.1.4" osenv "^0.1.4"
normalize-package-data@^2.3.2: normalize-package-data@^2.3.2:
version "2.4.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
integrity sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw== integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
dependencies: dependencies:
hosted-git-info "^2.1.4" hosted-git-info "^2.1.4"
is-builtin-module "^1.0.0" resolve "^1.10.0"
semver "2 || 3 || 4 || 5" semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1" validate-npm-package-license "^3.0.1"
@ -7666,13 +7640,13 @@ object.fromentries@^2.0.2:
function-bind "^1.1.1" function-bind "^1.1.1"
has "^1.0.3" has "^1.0.3"
object.getownpropertydescriptors@^2.0.3: object.getownpropertydescriptors@^2.1.0:
version "2.0.3" version "2.1.0"
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649"
integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==
dependencies: dependencies:
define-properties "^1.1.2" define-properties "^1.1.3"
es-abstract "^1.5.1" es-abstract "^1.17.0-next.1"
object.pick@^1.3.0: object.pick@^1.3.0:
version "1.3.0" version "1.3.0"
@ -7681,7 +7655,7 @@ object.pick@^1.3.0:
dependencies: dependencies:
isobject "^3.0.1" isobject "^3.0.1"
object.values@^1.0.4, object.values@^1.1.0, object.values@^1.1.1: object.values@^1.1.0, object.values@^1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e"
integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==
@ -8745,10 +8719,10 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
psl@^1.1.24, psl@^1.1.28: psl@^1.1.28:
version "1.1.31" version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
public-encrypt@^4.0.0: public-encrypt@^4.0.0:
version "4.0.3" version "4.0.3"
@ -8792,7 +8766,7 @@ punycode@1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
punycode@^1.2.4, punycode@^1.4.1: punycode@^1.2.4:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
@ -9223,10 +9197,10 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2" normalize-package-data "^2.3.2"
path-type "^3.0.0" path-type "^3.0.0"
"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: "readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6" version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
dependencies: dependencies:
core-util-is "~1.0.0" core-util-is "~1.0.0"
inherits "~2.0.3" inherits "~2.0.3"
@ -9236,10 +9210,10 @@ read-pkg@^3.0.0:
string_decoder "~1.1.1" string_decoder "~1.1.1"
util-deprecate "~1.0.1" util-deprecate "~1.0.1"
readable-stream@^2.0.2, readable-stream@^2.0.6: readable-stream@^2.0.1, readable-stream@^2.3.3:
version "2.3.7" version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
dependencies: dependencies:
core-util-is "~1.0.0" core-util-is "~1.0.0"
inherits "~2.0.3" inherits "~2.0.3"
@ -9493,9 +9467,9 @@ request-promise-native@^1.0.5:
tough-cookie ">=2.3.3" tough-cookie ">=2.3.3"
request@^2.87.0: request@^2.87.0:
version "2.88.0" version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
dependencies: dependencies:
aws-sign2 "~0.7.0" aws-sign2 "~0.7.0"
aws4 "^1.8.0" aws4 "^1.8.0"
@ -9504,7 +9478,7 @@ request@^2.87.0:
extend "~3.0.2" extend "~3.0.2"
forever-agent "~0.6.1" forever-agent "~0.6.1"
form-data "~2.3.2" form-data "~2.3.2"
har-validator "~5.1.0" har-validator "~5.1.3"
http-signature "~1.2.0" http-signature "~1.2.0"
is-typedarray "~1.0.0" is-typedarray "~1.0.0"
isstream "~0.1.2" isstream "~0.1.2"
@ -9514,7 +9488,7 @@ request@^2.87.0:
performance-now "^2.1.0" performance-now "^2.1.0"
qs "~6.5.2" qs "~6.5.2"
safe-buffer "^5.1.2" safe-buffer "^5.1.2"
tough-cookie "~2.4.3" tough-cookie "~2.5.0"
tunnel-agent "^0.6.0" tunnel-agent "^0.6.0"
uuid "^3.3.2" uuid "^3.3.2"
@ -9621,10 +9595,10 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1: resolve@^1.10.0, resolve@^1.12.0, resolve@^1.15.1, resolve@^1.3.2, resolve@^1.5.0, resolve@^1.8.1:
version "1.15.1" version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.1.tgz#27bdcdeffeaf2d6244b95bb0f9f4b4653451f3e8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w== integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies: dependencies:
path-parse "^1.0.6" path-parse "^1.0.6"
@ -10168,7 +10142,7 @@ source-map@0.5.6:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= integrity sha1-dc449SvwczxafwwRjYEzSiu19BI=
source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7: source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7:
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
@ -10187,9 +10161,9 @@ spdx-correct@^3.0.0:
spdx-license-ids "^3.0.0" spdx-license-ids "^3.0.0"
spdx-exceptions@^2.1.0: spdx-exceptions@^2.1.0:
version "2.2.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz#2ea450aee74f2a89bfb94519c07fcd6f41322977" resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
integrity sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA== integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
spdx-expression-parse@^3.0.0: spdx-expression-parse@^3.0.0:
version "3.0.0" version "3.0.0"
@ -10200,9 +10174,9 @@ spdx-expression-parse@^3.0.0:
spdx-license-ids "^3.0.0" spdx-license-ids "^3.0.0"
spdx-license-ids@^3.0.0: spdx-license-ids@^3.0.0:
version "3.0.3" version "3.0.5"
resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz#81c0ce8f21474756148bbb5f3bfc0f36bf15d76e" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz#3694b5804567a458d3c8045842a6358632f62654"
integrity sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g== integrity sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==
spdy-transport@^3.0.0: spdy-transport@^3.0.0:
version "3.0.0" version "3.0.0"
@ -10276,7 +10250,7 @@ ssri@^7.0.0:
figgy-pudding "^3.5.1" figgy-pudding "^3.5.1"
minipass "^3.1.1" minipass "^3.1.1"
stable@~0.1.6: stable@^0.1.8:
version "0.1.8" version "0.1.8"
resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf"
integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==
@ -10590,22 +10564,21 @@ supports-color@^7.0.0, supports-color@^7.1.0:
has-flag "^4.0.0" has-flag "^4.0.0"
svgo@^1.0.0: svgo@^1.0.0:
version "1.1.1" version "1.3.2"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
integrity sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g== integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==
dependencies: dependencies:
coa "~2.0.1" chalk "^2.4.1"
colors "~1.1.2" coa "^2.0.2"
css-select "^2.0.0" css-select "^2.0.0"
css-select-base-adapter "~0.1.0" css-select-base-adapter "^0.1.1"
css-tree "1.0.0-alpha.28" css-tree "1.0.0-alpha.37"
css-url-regex "^1.1.0" csso "^4.0.2"
csso "^3.5.0" js-yaml "^3.13.1"
js-yaml "^3.12.0"
mkdirp "~0.5.1" mkdirp "~0.5.1"
object.values "^1.0.4" object.values "^1.1.0"
sax "~1.2.4" sax "~1.2.4"
stable "~0.1.6" stable "^0.1.8"
unquote "~1.1.1" unquote "~1.1.1"
util.promisify "~1.0.0" util.promisify "~1.0.0"
@ -10873,7 +10846,7 @@ toidentifier@1.0.0:
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
tough-cookie@>=2.3.3, tough-cookie@^2.3.4: tough-cookie@>=2.3.3, tough-cookie@^2.3.4, tough-cookie@~2.5.0:
version "2.5.0" version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g== integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -10881,14 +10854,6 @@ tough-cookie@>=2.3.3, tough-cookie@^2.3.4:
psl "^1.1.28" psl "^1.1.28"
punycode "^2.1.1" punycode "^2.1.1"
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==
dependencies:
psl "^1.1.24"
punycode "^1.4.1"
tr46@^1.0.1: tr46@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -11104,12 +11069,14 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
util.promisify@^1.0.0, util.promisify@~1.0.0: util.promisify@^1.0.0, util.promisify@~1.0.0:
version "1.0.0" version "1.0.1"
resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.1.tgz#6baf7774b80eeb0f7520d8b81d07982a59abbaee"
integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== integrity sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==
dependencies: dependencies:
define-properties "^1.1.2" define-properties "^1.1.3"
object.getownpropertydescriptors "^2.0.3" es-abstract "^1.17.2"
has-symbols "^1.0.1"
object.getownpropertydescriptors "^2.1.0"
util@0.10.3: util@0.10.3:
version "0.10.3" version "0.10.3"