Merge branch 'master' into glitch-soc/merge-upstream
Conflicts: - README.md - app/javascript/styles/mastodon/components.scss conflicts caused by image URLs being different - app/models/status.rb as_home_timeline removed, kept glitch-soc-only as_direct_timeline - app/views/statuses/_simple_status.html.haml - config/locales/en.yml some strings were changed upstream - spec/models/status_spec.rb as_home_timeline removed, kept glitch-soc-only as_direct_timeline
This commit is contained in:
		
						commit
						61631f4751
					
				
							
								
								
									
										668
									
								
								AUTHORS.md
								
								
								
								
							
							
						
						
									
										668
									
								
								AUTHORS.md
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										33
									
								
								CHANGELOG.md
								
								
								
								
							
							
						
						
									
										33
									
								
								CHANGELOG.md
								
								
								
								
							| 
						 | 
					@ -3,6 +3,39 @@ Changelog
 | 
				
			||||||
 | 
					
 | 
				
			||||||
All notable changes to this project will be documented in this file.
 | 
					All notable changes to this project will be documented in this file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [3.0.1] - 2019-10-10
 | 
				
			||||||
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Add `tootctl media usage` command ([Gargron](https://github.com/tootsuite/mastodon/pull/12115))
 | 
				
			||||||
 | 
					- Add admin setting to auto-approve trending hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12122), [Gargron](https://github.com/tootsuite/mastodon/pull/12130))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Changed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Change `tootctl media refresh` to skip already downloaded attachments ([Gargron](https://github.com/tootsuite/mastodon/pull/12118))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Removed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Remove auto-silence behaviour from spam check ([Gargron](https://github.com/tootsuite/mastodon/pull/12117))
 | 
				
			||||||
 | 
					- Remove HTML `lang` attribute from individual statuses in web UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12124))
 | 
				
			||||||
 | 
					- Remove fallback to long description on sidebar and meta description ([Gargron](https://github.com/tootsuite/mastodon/pull/12119))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Fixed
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fix preloaded JSON-LD context for identity not being used ([Gargron](https://github.com/tootsuite/mastodon/pull/12138))
 | 
				
			||||||
 | 
					- Fix media editing modal changing dimensions once the image loads ([Gargron](https://github.com/tootsuite/mastodon/pull/12131))
 | 
				
			||||||
 | 
					- Fix not showing whether a custom emoji has a local counterpart in admin UI ([Gargron](https://github.com/tootsuite/mastodon/pull/12135))
 | 
				
			||||||
 | 
					- Fix attachment not being re-downloaded even if file is not stored ([Gargron](https://github.com/tootsuite/mastodon/pull/12125))
 | 
				
			||||||
 | 
					- Fix old migration trying to use new column due to default status scope ([Gargron](https://github.com/tootsuite/mastodon/pull/12095))
 | 
				
			||||||
 | 
					- Fix column back button missing for not found accounts ([trwnh](https://github.com/tootsuite/mastodon/pull/12094))
 | 
				
			||||||
 | 
					- Fix issues with tootctl's parallelization and progress reporting ([Gargron](https://github.com/tootsuite/mastodon/pull/12093), [Gargron](https://github.com/tootsuite/mastodon/pull/12097))
 | 
				
			||||||
 | 
					- Fix existing user records with now-renamed `pt` locale ([Gargron](https://github.com/tootsuite/mastodon/pull/12092))
 | 
				
			||||||
 | 
					- Fix hashtag timeline REST API accepting too many hashtags ([Gargron](https://github.com/tootsuite/mastodon/pull/12091))
 | 
				
			||||||
 | 
					- Fix `GET /api/v1/instance` REST APIs being unavailable in secure mode ([Gargron](https://github.com/tootsuite/mastodon/pull/12089))
 | 
				
			||||||
 | 
					- Fix performance of home feed regeneration and merging ([Gargron](https://github.com/tootsuite/mastodon/pull/12084))
 | 
				
			||||||
 | 
					- Fix ffmpeg performance issues due to stdout buffer overflow ([hugogameiro](https://github.com/tootsuite/mastodon/pull/12088))
 | 
				
			||||||
 | 
					- Fix S3 adapter retrying failing uploads with exponential backoff ([Gargron](https://github.com/tootsuite/mastodon/pull/12085))
 | 
				
			||||||
 | 
					- Fix `tootctl accounts cull` advertising unused option flag ([Kjwon15](https://github.com/tootsuite/mastodon/pull/12074))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [3.0.0] - 2019-10-03
 | 
					## [3.0.0] - 2019-10-03
 | 
				
			||||||
### Added
 | 
					### Added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										4
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										4
									
								
								Gemfile
								
								
								
								
							| 
						 | 
					@ -90,7 +90,7 @@ gem 'simple_form', '~> 4.1'
 | 
				
			||||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 | 
					gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
 | 
				
			||||||
gem 'stoplight', '~> 2.1.3'
 | 
					gem 'stoplight', '~> 2.1.3'
 | 
				
			||||||
gem 'strong_migrations', '~> 0.4'
 | 
					gem 'strong_migrations', '~> 0.4'
 | 
				
			||||||
gem 'tty-command', '~> 0.8', require: false
 | 
					gem 'tty-command', '~> 0.9', require: false
 | 
				
			||||||
gem 'tty-prompt', '~> 0.19', require: false
 | 
					gem 'tty-prompt', '~> 0.19', require: false
 | 
				
			||||||
gem 'twitter-text', '~> 1.14'
 | 
					gem 'twitter-text', '~> 1.14'
 | 
				
			||||||
gem 'tzinfo-data', '~> 1.2019'
 | 
					gem 'tzinfo-data', '~> 1.2019'
 | 
				
			||||||
| 
						 | 
					@ -119,7 +119,7 @@ end
 | 
				
			||||||
group :test do
 | 
					group :test do
 | 
				
			||||||
  gem 'capybara', '~> 3.29'
 | 
					  gem 'capybara', '~> 3.29'
 | 
				
			||||||
  gem 'climate_control', '~> 0.2'
 | 
					  gem 'climate_control', '~> 0.2'
 | 
				
			||||||
  gem 'faker', '~> 2.4'
 | 
					  gem 'faker', '~> 2.5'
 | 
				
			||||||
  gem 'microformats', '~> 4.1'
 | 
					  gem 'microformats', '~> 4.1'
 | 
				
			||||||
  gem 'rails-controller-testing', '~> 1.0'
 | 
					  gem 'rails-controller-testing', '~> 1.0'
 | 
				
			||||||
  gem 'rspec-sidekiq', '~> 3.0'
 | 
					  gem 'rspec-sidekiq', '~> 3.0'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										33
									
								
								Gemfile.lock
								
								
								
								
							| 
						 | 
					@ -93,7 +93,7 @@ GEM
 | 
				
			||||||
      tzinfo (~> 1.1)
 | 
					      tzinfo (~> 1.1)
 | 
				
			||||||
    addressable (2.7.0)
 | 
					    addressable (2.7.0)
 | 
				
			||||||
      public_suffix (>= 2.0.2, < 5.0)
 | 
					      public_suffix (>= 2.0.2, < 5.0)
 | 
				
			||||||
    airbrussh (1.3.3)
 | 
					    airbrussh (1.3.4)
 | 
				
			||||||
      sshkit (>= 1.6.1, != 1.7.0)
 | 
					      sshkit (>= 1.6.1, != 1.7.0)
 | 
				
			||||||
    annotate (2.7.5)
 | 
					    annotate (2.7.5)
 | 
				
			||||||
      activerecord (>= 3.2, < 7.0)
 | 
					      activerecord (>= 3.2, < 7.0)
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,7 @@ GEM
 | 
				
			||||||
      bundler (>= 1.2.0, < 3)
 | 
					      bundler (>= 1.2.0, < 3)
 | 
				
			||||||
      thor (~> 0.18)
 | 
					      thor (~> 0.18)
 | 
				
			||||||
    byebug (11.0.0)
 | 
					    byebug (11.0.0)
 | 
				
			||||||
    capistrano (3.11.1)
 | 
					    capistrano (3.11.2)
 | 
				
			||||||
      airbrussh (>= 1.0.0)
 | 
					      airbrussh (>= 1.0.0)
 | 
				
			||||||
      i18n
 | 
					      i18n
 | 
				
			||||||
      rake (>= 10.0.0)
 | 
					      rake (>= 10.0.0)
 | 
				
			||||||
| 
						 | 
					@ -188,13 +188,14 @@ GEM
 | 
				
			||||||
    css_parser (1.7.0)
 | 
					    css_parser (1.7.0)
 | 
				
			||||||
      addressable
 | 
					      addressable
 | 
				
			||||||
    debug_inspector (0.0.3)
 | 
					    debug_inspector (0.0.3)
 | 
				
			||||||
    derailed_benchmarks (1.3.6)
 | 
					    derailed_benchmarks (1.4.0)
 | 
				
			||||||
      benchmark-ips (~> 2)
 | 
					      benchmark-ips (~> 2)
 | 
				
			||||||
      get_process_mem (~> 0)
 | 
					      get_process_mem (~> 0)
 | 
				
			||||||
      heapy (~> 0)
 | 
					      heapy (~> 0)
 | 
				
			||||||
      memory_profiler (~> 0)
 | 
					      memory_profiler (~> 0)
 | 
				
			||||||
      rack (>= 1)
 | 
					      rack (>= 1)
 | 
				
			||||||
      rake (> 10, < 13)
 | 
					      rake (> 10, < 13)
 | 
				
			||||||
 | 
					      ruby-statistics (>= 2.1)
 | 
				
			||||||
      thor (~> 0.19)
 | 
					      thor (~> 0.19)
 | 
				
			||||||
    devise (4.7.1)
 | 
					    devise (4.7.1)
 | 
				
			||||||
      bcrypt (~> 3.0)
 | 
					      bcrypt (~> 3.0)
 | 
				
			||||||
| 
						 | 
					@ -233,13 +234,13 @@ GEM
 | 
				
			||||||
      faraday
 | 
					      faraday
 | 
				
			||||||
      multi_json
 | 
					      multi_json
 | 
				
			||||||
    encryptor (3.0.0)
 | 
					    encryptor (3.0.0)
 | 
				
			||||||
    equatable (0.5.0)
 | 
					    equatable (0.6.1)
 | 
				
			||||||
    erubi (1.8.0)
 | 
					    erubi (1.8.0)
 | 
				
			||||||
    et-orbi (1.1.6)
 | 
					    et-orbi (1.1.6)
 | 
				
			||||||
      tzinfo
 | 
					      tzinfo
 | 
				
			||||||
    excon (0.62.0)
 | 
					    excon (0.62.0)
 | 
				
			||||||
    fabrication (2.20.2)
 | 
					    fabrication (2.20.2)
 | 
				
			||||||
    faker (2.4.0)
 | 
					    faker (2.5.0)
 | 
				
			||||||
      i18n (~> 1.6.0)
 | 
					      i18n (~> 1.6.0)
 | 
				
			||||||
    faraday (0.15.4)
 | 
					    faraday (0.15.4)
 | 
				
			||||||
      multipart-post (>= 1.2, < 3)
 | 
					      multipart-post (>= 1.2, < 3)
 | 
				
			||||||
| 
						 | 
					@ -265,7 +266,8 @@ GEM
 | 
				
			||||||
    fuubar (2.4.1)
 | 
					    fuubar (2.4.1)
 | 
				
			||||||
      rspec-core (~> 3.0)
 | 
					      rspec-core (~> 3.0)
 | 
				
			||||||
      ruby-progressbar (~> 1.4)
 | 
					      ruby-progressbar (~> 1.4)
 | 
				
			||||||
    get_process_mem (0.2.3)
 | 
					    get_process_mem (0.2.4)
 | 
				
			||||||
 | 
					      ffi (~> 1.0)
 | 
				
			||||||
    globalid (0.4.2)
 | 
					    globalid (0.4.2)
 | 
				
			||||||
      activesupport (>= 4.2.0)
 | 
					      activesupport (>= 4.2.0)
 | 
				
			||||||
    goldfinger (2.1.0)
 | 
					    goldfinger (2.1.0)
 | 
				
			||||||
| 
						 | 
					@ -429,13 +431,13 @@ GEM
 | 
				
			||||||
    parser (2.6.4.0)
 | 
					    parser (2.6.4.0)
 | 
				
			||||||
      ast (~> 2.4.0)
 | 
					      ast (~> 2.4.0)
 | 
				
			||||||
    parslet (1.8.2)
 | 
					    parslet (1.8.2)
 | 
				
			||||||
    pastel (0.7.2)
 | 
					    pastel (0.7.3)
 | 
				
			||||||
      equatable (~> 0.5.0)
 | 
					      equatable (~> 0.6)
 | 
				
			||||||
      tty-color (~> 0.4.0)
 | 
					      tty-color (~> 0.5)
 | 
				
			||||||
    pg (1.1.4)
 | 
					    pg (1.1.4)
 | 
				
			||||||
    pghero (2.3.0)
 | 
					    pghero (2.3.0)
 | 
				
			||||||
      activerecord (>= 5)
 | 
					      activerecord (>= 5)
 | 
				
			||||||
    pkg-config (1.3.8)
 | 
					    pkg-config (1.3.9)
 | 
				
			||||||
    premailer (1.11.1)
 | 
					    premailer (1.11.1)
 | 
				
			||||||
      addressable
 | 
					      addressable
 | 
				
			||||||
      css_parser (>= 1.6.0)
 | 
					      css_parser (>= 1.6.0)
 | 
				
			||||||
| 
						 | 
					@ -571,6 +573,7 @@ GEM
 | 
				
			||||||
    ruby-progressbar (1.10.1)
 | 
					    ruby-progressbar (1.10.1)
 | 
				
			||||||
    ruby-saml (1.9.0)
 | 
					    ruby-saml (1.9.0)
 | 
				
			||||||
      nokogiri (>= 1.5.10)
 | 
					      nokogiri (>= 1.5.10)
 | 
				
			||||||
 | 
					    ruby-statistics (2.1.1)
 | 
				
			||||||
    rufus-scheduler (3.5.2)
 | 
					    rufus-scheduler (3.5.2)
 | 
				
			||||||
      fugit (~> 1.1, >= 1.1.5)
 | 
					      fugit (~> 1.1, >= 1.1.5)
 | 
				
			||||||
    safe_yaml (1.0.5)
 | 
					    safe_yaml (1.0.5)
 | 
				
			||||||
| 
						 | 
					@ -629,8 +632,8 @@ GEM
 | 
				
			||||||
    thor (0.20.3)
 | 
					    thor (0.20.3)
 | 
				
			||||||
    thread_safe (0.3.6)
 | 
					    thread_safe (0.3.6)
 | 
				
			||||||
    tilt (2.0.9)
 | 
					    tilt (2.0.9)
 | 
				
			||||||
    tty-color (0.4.3)
 | 
					    tty-color (0.5.0)
 | 
				
			||||||
    tty-command (0.8.2)
 | 
					    tty-command (0.9.0)
 | 
				
			||||||
      pastel (~> 0.7.0)
 | 
					      pastel (~> 0.7.0)
 | 
				
			||||||
    tty-cursor (0.7.0)
 | 
					    tty-cursor (0.7.0)
 | 
				
			||||||
    tty-prompt (0.19.0)
 | 
					    tty-prompt (0.19.0)
 | 
				
			||||||
| 
						 | 
					@ -655,7 +658,7 @@ GEM
 | 
				
			||||||
    uniform_notifier (1.12.1)
 | 
					    uniform_notifier (1.12.1)
 | 
				
			||||||
    warden (1.2.8)
 | 
					    warden (1.2.8)
 | 
				
			||||||
      rack (>= 2.0.6)
 | 
					      rack (>= 2.0.6)
 | 
				
			||||||
    webmock (3.7.5)
 | 
					    webmock (3.7.6)
 | 
				
			||||||
      addressable (>= 2.3.6)
 | 
					      addressable (>= 2.3.6)
 | 
				
			||||||
      crack (>= 0.3.2)
 | 
					      crack (>= 0.3.2)
 | 
				
			||||||
      hashdiff (>= 0.4.0, < 2.0.0)
 | 
					      hashdiff (>= 0.4.0, < 2.0.0)
 | 
				
			||||||
| 
						 | 
					@ -709,7 +712,7 @@ DEPENDENCIES
 | 
				
			||||||
  doorkeeper (~> 5.2)
 | 
					  doorkeeper (~> 5.2)
 | 
				
			||||||
  dotenv-rails (~> 2.7)
 | 
					  dotenv-rails (~> 2.7)
 | 
				
			||||||
  fabrication (~> 2.20)
 | 
					  fabrication (~> 2.20)
 | 
				
			||||||
  faker (~> 2.4)
 | 
					  faker (~> 2.5)
 | 
				
			||||||
  fast_blank (~> 1.0)
 | 
					  fast_blank (~> 1.0)
 | 
				
			||||||
  fastimage
 | 
					  fastimage
 | 
				
			||||||
  fog-core (<= 2.1.0)
 | 
					  fog-core (<= 2.1.0)
 | 
				
			||||||
| 
						 | 
					@ -796,7 +799,7 @@ DEPENDENCIES
 | 
				
			||||||
  streamio-ffmpeg (~> 3.0)
 | 
					  streamio-ffmpeg (~> 3.0)
 | 
				
			||||||
  strong_migrations (~> 0.4)
 | 
					  strong_migrations (~> 0.4)
 | 
				
			||||||
  thor (~> 0.20)
 | 
					  thor (~> 0.20)
 | 
				
			||||||
  tty-command (~> 0.8)
 | 
					  tty-command (~> 0.9)
 | 
				
			||||||
  tty-prompt (~> 0.19)
 | 
					  tty-prompt (~> 0.19)
 | 
				
			||||||
  twitter-text (~> 1.14)
 | 
					  twitter-text (~> 1.14)
 | 
				
			||||||
  tzinfo-data (~> 1.2019)
 | 
					  tzinfo-data (~> 1.2019)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ class Api::V1::Instances::ActivityController < Api::BaseController
 | 
				
			||||||
  before_action :require_enabled_api!
 | 
					  before_action :require_enabled_api!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  skip_before_action :set_cache_headers
 | 
					  skip_before_action :set_cache_headers
 | 
				
			||||||
 | 
					  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  respond_to :json
 | 
					  respond_to :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
 | 
				
			||||||
  before_action :require_enabled_api!
 | 
					  before_action :require_enabled_api!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  skip_before_action :set_cache_headers
 | 
					  skip_before_action :set_cache_headers
 | 
				
			||||||
 | 
					  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  respond_to :json
 | 
					  respond_to :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ class Api::V1::InstancesController < Api::BaseController
 | 
				
			||||||
  respond_to :json
 | 
					  respond_to :json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  skip_before_action :set_cache_headers
 | 
					  skip_before_action :set_cache_headers
 | 
				
			||||||
 | 
					  skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def show
 | 
					  def show
 | 
				
			||||||
    expires_in 3.minutes, public: true
 | 
					    expires_in 3.minutes, public: true
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,17 @@ class Api::V1::StreamingController < Api::BaseController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def index
 | 
					  def index
 | 
				
			||||||
    if Rails.configuration.x.streaming_api_base_url != request.host
 | 
					    if Rails.configuration.x.streaming_api_base_url != request.host
 | 
				
			||||||
      uri = URI.parse(request.url)
 | 
					      redirect_to streaming_api_url, status: 301
 | 
				
			||||||
      uri.host = URI.parse(Rails.configuration.x.streaming_api_base_url).host
 | 
					 | 
				
			||||||
      redirect_to uri.to_s, status: 301
 | 
					 | 
				
			||||||
    else
 | 
					    else
 | 
				
			||||||
      raise ActiveRecord::RecordNotFound
 | 
					      not_found
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def streaming_api_url
 | 
				
			||||||
 | 
					    Addressable::URI.parse(request.url).tap do |uri|
 | 
				
			||||||
 | 
					      uri.host = Addressable::URI.parse(Rails.configuration.x.streaming_api_base_url).host
 | 
				
			||||||
 | 
					    end.to_s
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ class Api::V1::Timelines::HomeController < Api::BaseController
 | 
				
			||||||
    render json: @statuses,
 | 
					    render json: @statuses,
 | 
				
			||||||
           each_serializer: REST::StatusSerializer,
 | 
					           each_serializer: REST::StatusSerializer,
 | 
				
			||||||
           relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
 | 
					           relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id),
 | 
				
			||||||
           status: regeneration_in_progress? ? 206 : 200
 | 
					           status: account_home_feed.regenerating? ? 206 : 200
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
| 
						 | 
					@ -62,8 +62,4 @@ class Api::V1::Timelines::HomeController < Api::BaseController
 | 
				
			||||||
  def pagination_since_id
 | 
					  def pagination_since_id
 | 
				
			||||||
    @statuses.first.id
 | 
					    @statuses.first.id
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def regeneration_in_progress?
 | 
					 | 
				
			||||||
    Redis.current.exists("account:#{current_account.id}:regeneration")
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -97,7 +97,7 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
 | 
				
			||||||
    api(getState).get(path, { params }).then(response => {
 | 
					    api(getState).get(path, { params }).then(response => {
 | 
				
			||||||
      const next = getLinks(response).refs.find(link => link.rel === 'next');
 | 
					      const next = getLinks(response).refs.find(link => link.rel === 'next');
 | 
				
			||||||
      dispatch(importFetchedStatuses(response.data));
 | 
					      dispatch(importFetchedStatuses(response.data));
 | 
				
			||||||
      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.code === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
 | 
					      dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
 | 
				
			||||||
      done();
 | 
					      done();
 | 
				
			||||||
    }).catch(error => {
 | 
					    }).catch(error => {
 | 
				
			||||||
      dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
 | 
					      dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,63 +0,0 @@
 | 
				
			||||||
import React from 'react';
 | 
					 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export default class ExtendedVideoPlayer extends React.PureComponent {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  static propTypes = {
 | 
					 | 
				
			||||||
    src: PropTypes.string.isRequired,
 | 
					 | 
				
			||||||
    alt: PropTypes.string,
 | 
					 | 
				
			||||||
    width: PropTypes.number,
 | 
					 | 
				
			||||||
    height: PropTypes.number,
 | 
					 | 
				
			||||||
    time: PropTypes.number,
 | 
					 | 
				
			||||||
    controls: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
    muted: PropTypes.bool.isRequired,
 | 
					 | 
				
			||||||
    onClick: PropTypes.func,
 | 
					 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleLoadedData = () => {
 | 
					 | 
				
			||||||
    if (this.props.time) {
 | 
					 | 
				
			||||||
      this.video.currentTime = this.props.time;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentDidMount () {
 | 
					 | 
				
			||||||
    this.video.addEventListener('loadeddata', this.handleLoadedData);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  componentWillUnmount () {
 | 
					 | 
				
			||||||
    this.video.removeEventListener('loadeddata', this.handleLoadedData);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  setRef = (c) => {
 | 
					 | 
				
			||||||
    this.video = c;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  handleClick = e => {
 | 
					 | 
				
			||||||
    e.stopPropagation();
 | 
					 | 
				
			||||||
    const handler = this.props.onClick;
 | 
					 | 
				
			||||||
    if (handler) handler();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  render () {
 | 
					 | 
				
			||||||
    const { src, muted, controls, alt } = this.props;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
      <div className='extended-video-player'>
 | 
					 | 
				
			||||||
        <video
 | 
					 | 
				
			||||||
          ref={this.setRef}
 | 
					 | 
				
			||||||
          src={src}
 | 
					 | 
				
			||||||
          autoPlay
 | 
					 | 
				
			||||||
          role='button'
 | 
					 | 
				
			||||||
          tabIndex='0'
 | 
					 | 
				
			||||||
          aria-label={alt}
 | 
					 | 
				
			||||||
          title={alt}
 | 
					 | 
				
			||||||
          muted={muted}
 | 
					 | 
				
			||||||
          controls={controls}
 | 
					 | 
				
			||||||
          loop={!controls}
 | 
					 | 
				
			||||||
          onClick={this.handleClick}
 | 
					 | 
				
			||||||
        />
 | 
					 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class GIFV extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static propTypes = {
 | 
				
			||||||
 | 
					    src: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    alt: PropTypes.string,
 | 
				
			||||||
 | 
					    width: PropTypes.number,
 | 
				
			||||||
 | 
					    height: PropTypes.number,
 | 
				
			||||||
 | 
					    onClick: PropTypes.func,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state = {
 | 
				
			||||||
 | 
					    loading: true,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleLoadedData = () => {
 | 
				
			||||||
 | 
					    this.setState({ loading: false });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentWillReceiveProps (nextProps) {
 | 
				
			||||||
 | 
					    if (nextProps.src !== this.props.src) {
 | 
				
			||||||
 | 
					      this.setState({ loading: true });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  handleClick = e => {
 | 
				
			||||||
 | 
					    const { onClick } = this.props;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (onClick) {
 | 
				
			||||||
 | 
					      e.stopPropagation();
 | 
				
			||||||
 | 
					      onClick();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { src, width, height, alt } = this.props;
 | 
				
			||||||
 | 
					    const { loading } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					      <div className='gifv' style={{ position: 'relative' }}>
 | 
				
			||||||
 | 
					        {loading && (
 | 
				
			||||||
 | 
					          <canvas
 | 
				
			||||||
 | 
					            width={width}
 | 
				
			||||||
 | 
					            height={height}
 | 
				
			||||||
 | 
					            role='button'
 | 
				
			||||||
 | 
					            tabIndex='0'
 | 
				
			||||||
 | 
					            aria-label={alt}
 | 
				
			||||||
 | 
					            title={alt}
 | 
				
			||||||
 | 
					            onClick={this.handleClick}
 | 
				
			||||||
 | 
					          />
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <video
 | 
				
			||||||
 | 
					          src={src}
 | 
				
			||||||
 | 
					          width={width}
 | 
				
			||||||
 | 
					          height={height}
 | 
				
			||||||
 | 
					          role='button'
 | 
				
			||||||
 | 
					          tabIndex='0'
 | 
				
			||||||
 | 
					          aria-label={alt}
 | 
				
			||||||
 | 
					          title={alt}
 | 
				
			||||||
 | 
					          muted
 | 
				
			||||||
 | 
					          loop
 | 
				
			||||||
 | 
					          autoPlay
 | 
				
			||||||
 | 
					          playsInline
 | 
				
			||||||
 | 
					          onClick={this.handleClick}
 | 
				
			||||||
 | 
					          onLoadedData={this.handleLoadedData}
 | 
				
			||||||
 | 
					          style={{ position: loading ? 'absolute' : 'static', top: 0, left: 0 }}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -1,17 +1,24 @@
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					import illustration from 'mastodon/../images/elephant_ui_disappointed.svg';
 | 
				
			||||||
 | 
					import classNames from 'classnames';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MissingIndicator = () => (
 | 
					const MissingIndicator = ({ fullPage }) => (
 | 
				
			||||||
  <div className='regeneration-indicator missing-indicator'>
 | 
					  <div className={classNames('regeneration-indicator', { 'regeneration-indicator--without-header': fullPage })}>
 | 
				
			||||||
    <div>
 | 
					    <div className='regeneration-indicator__figure'>
 | 
				
			||||||
      <div className='regeneration-indicator__figure' />
 | 
					      <img src={illustration} alt='' />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      <div className='regeneration-indicator__label'>
 | 
					    <div className='regeneration-indicator__label'>
 | 
				
			||||||
        <FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
 | 
					      <FormattedMessage id='missing_indicator.label' tagName='strong' defaultMessage='Not found' />
 | 
				
			||||||
        <FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
 | 
					      <FormattedMessage id='missing_indicator.sublabel' defaultMessage='This resource could not be found' />
 | 
				
			||||||
      </div>
 | 
					 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MissingIndicator.propTypes = {
 | 
				
			||||||
 | 
					  fullPage: PropTypes.bool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default MissingIndicator;
 | 
					export default MissingIndicator;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,18 @@
 | 
				
			||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					import { FormattedMessage } from 'react-intl';
 | 
				
			||||||
 | 
					import illustration from 'mastodon/../images/elephant_ui_working.svg';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MissingIndicator = () => (
 | 
				
			||||||
 | 
					  <div className='regeneration-indicator'>
 | 
				
			||||||
 | 
					    <div className='regeneration-indicator__figure'>
 | 
				
			||||||
 | 
					      <img src={illustration} alt='' />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div className='regeneration-indicator__label'>
 | 
				
			||||||
 | 
					      <FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
 | 
				
			||||||
 | 
					      <FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default MissingIndicator;
 | 
				
			||||||
| 
						 | 
					@ -216,14 +216,14 @@ export default class StatusContent extends React.PureComponent {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
 | 
					        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp}>
 | 
				
			||||||
          <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
 | 
					          <p style={{ marginBottom: hidden && status.get('mentions').isEmpty() ? '0px' : null }}>
 | 
				
			||||||
            <span dangerouslySetInnerHTML={spoilerContent} lang={status.get('language')} />
 | 
					            <span dangerouslySetInnerHTML={spoilerContent} />
 | 
				
			||||||
            {' '}
 | 
					            {' '}
 | 
				
			||||||
            <button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
 | 
					            <button tabIndex='0' className={`status__content__spoiler-link ${hidden ? 'status__content__spoiler-link--show-more' : 'status__content__spoiler-link--show-less'}`} onClick={this.handleSpoilerClick}>{toggleText}</button>
 | 
				
			||||||
          </p>
 | 
					          </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {mentionsPlaceholder}
 | 
					          {mentionsPlaceholder}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
 | 
					          <div tabIndex={!hidden ? 0 : null} className={`status__content__text ${!hidden ? 'status__content__text--visible' : ''}`} style={directionStyle} dangerouslySetInnerHTML={content} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
					          {!hidden && !!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					@ -231,7 +231,7 @@ export default class StatusContent extends React.PureComponent {
 | 
				
			||||||
    } else if (this.props.onClick) {
 | 
					    } else if (this.props.onClick) {
 | 
				
			||||||
      const output = [
 | 
					      const output = [
 | 
				
			||||||
        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
 | 
					        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle} onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} key='status-content'>
 | 
				
			||||||
          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
 | 
					          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
					          {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
				
			||||||
        </div>,
 | 
					        </div>,
 | 
				
			||||||
| 
						 | 
					@ -245,7 +245,7 @@ export default class StatusContent extends React.PureComponent {
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
 | 
					        <div className={classNames} ref={this.setRef} tabIndex='0' style={directionStyle}>
 | 
				
			||||||
          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} lang={status.get('language')} />
 | 
					          <div className='status__content__text status__content__text--visible' style={directionStyle} dangerouslySetInnerHTML={content} />
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
					          {!!status.get('poll') && <PollContainer pollId={status.get('poll')} />}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
import { debounce } from 'lodash';
 | 
					import { debounce } from 'lodash';
 | 
				
			||||||
import React from 'react';
 | 
					import React from 'react';
 | 
				
			||||||
import { FormattedMessage } from 'react-intl';
 | 
					 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import StatusContainer from '../containers/status_container';
 | 
					import StatusContainer from '../containers/status_container';
 | 
				
			||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
					import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
				
			||||||
import LoadGap from './load_gap';
 | 
					import LoadGap from './load_gap';
 | 
				
			||||||
import ScrollableList from './scrollable_list';
 | 
					import ScrollableList from './scrollable_list';
 | 
				
			||||||
 | 
					import RegenerationIndicator from 'mastodon/components/regeneration_indicator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class StatusList extends ImmutablePureComponent {
 | 
					export default class StatusList extends ImmutablePureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -81,18 +81,7 @@ export default class StatusList extends ImmutablePureComponent {
 | 
				
			||||||
    const { isLoading, isPartial } = other;
 | 
					    const { isLoading, isPartial } = other;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (isPartial) {
 | 
					    if (isPartial) {
 | 
				
			||||||
      return (
 | 
					      return <RegenerationIndicator />;
 | 
				
			||||||
        <div className='regeneration-indicator'>
 | 
					 | 
				
			||||||
          <div>
 | 
					 | 
				
			||||||
            <div className='regeneration-indicator__figure' />
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            <div className='regeneration-indicator__label'>
 | 
					 | 
				
			||||||
              <FormattedMessage id='regeneration_indicator.label' tagName='strong' defaultMessage='Loading…' />
 | 
					 | 
				
			||||||
              <FormattedMessage id='regeneration_indicator.sublabel' defaultMessage='Your home feed is being prepared!' />
 | 
					 | 
				
			||||||
            </div>
 | 
					 | 
				
			||||||
          </div>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
      );
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let scrollableContent = (isLoading || statusIds.size > 0) ? (
 | 
					    let scrollableContent = (isLoading || statusIds.size > 0) ? (
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,6 +83,7 @@ class AccountTimeline extends ImmutablePureComponent {
 | 
				
			||||||
    if (!isAccount) {
 | 
					    if (!isAccount) {
 | 
				
			||||||
      return (
 | 
					      return (
 | 
				
			||||||
        <Column>
 | 
					        <Column>
 | 
				
			||||||
 | 
					          <ColumnBackButton multiColumn={multiColumn} />
 | 
				
			||||||
          <MissingIndicator />
 | 
					          <MissingIndicator />
 | 
				
			||||||
        </Column>
 | 
					        </Column>
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ import MissingIndicator from '../../components/missing_indicator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const GenericNotFound = () => (
 | 
					const GenericNotFound = () => (
 | 
				
			||||||
  <Column>
 | 
					  <Column>
 | 
				
			||||||
    <MissingIndicator />
 | 
					    <MissingIndicator fullPage />
 | 
				
			||||||
  </Column>
 | 
					  </Column>
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,6 +16,7 @@ import UploadProgress from 'mastodon/features/compose/components/upload_progress
 | 
				
			||||||
import CharacterCounter from 'mastodon/features/compose/components/character_counter';
 | 
					import CharacterCounter from 'mastodon/features/compose/components/character_counter';
 | 
				
			||||||
import { length } from 'stringz';
 | 
					import { length } from 'stringz';
 | 
				
			||||||
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
 | 
					import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
 | 
				
			||||||
 | 
					import GIFV from 'mastodon/components/gifv';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
					  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
				
			||||||
| 
						 | 
					@ -41,6 +42,36 @@ const removeExtraLineBreaks = str => str.replace(/\n\n/g, '******')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const assetHost = process.env.CDN_HOST || '';
 | 
					const assetHost = process.env.CDN_HOST || '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ImageLoader extends React.PureComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  static propTypes = {
 | 
				
			||||||
 | 
					    src: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    width: PropTypes.number,
 | 
				
			||||||
 | 
					    height: PropTypes.number,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  state = {
 | 
				
			||||||
 | 
					    loading: true,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  componentDidMount() {
 | 
				
			||||||
 | 
					    const image = new Image();
 | 
				
			||||||
 | 
					    image.addEventListener('load', () => this.setState({ loading: false }));
 | 
				
			||||||
 | 
					    image.src = this.props.src;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  render () {
 | 
				
			||||||
 | 
					    const { loading } = this.state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (loading) {
 | 
				
			||||||
 | 
					      return <canvas width={this.props.width} height={this.props.height} />;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return <img {...this.props} alt='' />;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default @connect(mapStateToProps, mapDispatchToProps)
 | 
					export default @connect(mapStateToProps, mapDispatchToProps)
 | 
				
			||||||
@injectIntl
 | 
					@injectIntl
 | 
				
			||||||
class FocalPointModal extends ImmutablePureComponent {
 | 
					class FocalPointModal extends ImmutablePureComponent {
 | 
				
			||||||
| 
						 | 
					@ -60,6 +91,7 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
				
			||||||
    description: '',
 | 
					    description: '',
 | 
				
			||||||
    dirty: false,
 | 
					    dirty: false,
 | 
				
			||||||
    progress: 0,
 | 
					    progress: 0,
 | 
				
			||||||
 | 
					    loading: true,
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  componentWillMount () {
 | 
					  componentWillMount () {
 | 
				
			||||||
| 
						 | 
					@ -242,8 +274,8 @@ class FocalPointModal extends ImmutablePureComponent {
 | 
				
			||||||
          <div className='focal-point-modal__content'>
 | 
					          <div className='focal-point-modal__content'>
 | 
				
			||||||
            {focals && (
 | 
					            {focals && (
 | 
				
			||||||
              <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
 | 
					              <div className={classNames('focal-point', { dragging })} ref={this.setRef} onMouseDown={this.handleMouseDown} onTouchStart={this.handleTouchStart}>
 | 
				
			||||||
                {media.get('type') === 'image' && <img src={media.get('url')} width={width} height={height} alt='' />}
 | 
					                {media.get('type') === 'image' && <ImageLoader src={media.get('url')} width={width} height={height} alt='' />}
 | 
				
			||||||
                {media.get('type') === 'gifv' && <video src={media.get('url')} width={width} height={height} loop muted autoPlay />}
 | 
					                {media.get('type') === 'gifv' && <GIFV src={media.get('url')} width={width} height={height} />}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                <div className='focal-point__preview'>
 | 
					                <div className='focal-point__preview'>
 | 
				
			||||||
                  <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
 | 
					                  <strong><FormattedMessage id='upload_modal.preview_label' defaultMessage='Preview ({ratio})' values={{ ratio: '16:9' }} /></strong>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,13 +3,13 @@ import ReactSwipeableViews from 'react-swipeable-views';
 | 
				
			||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
					import ImmutablePropTypes from 'react-immutable-proptypes';
 | 
				
			||||||
import PropTypes from 'prop-types';
 | 
					import PropTypes from 'prop-types';
 | 
				
			||||||
import Video from 'mastodon/features/video';
 | 
					import Video from 'mastodon/features/video';
 | 
				
			||||||
import ExtendedVideoPlayer from 'mastodon/components/extended_video_player';
 | 
					 | 
				
			||||||
import classNames from 'classnames';
 | 
					import classNames from 'classnames';
 | 
				
			||||||
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
					import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
 | 
				
			||||||
import IconButton from 'mastodon/components/icon_button';
 | 
					import IconButton from 'mastodon/components/icon_button';
 | 
				
			||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
					import ImmutablePureComponent from 'react-immutable-pure-component';
 | 
				
			||||||
import ImageLoader from './image_loader';
 | 
					import ImageLoader from './image_loader';
 | 
				
			||||||
import Icon from 'mastodon/components/icon';
 | 
					import Icon from 'mastodon/components/icon';
 | 
				
			||||||
 | 
					import GIFV from 'mastodon/components/gifv';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const messages = defineMessages({
 | 
					const messages = defineMessages({
 | 
				
			||||||
  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
					  close: { id: 'lightbox.close', defaultMessage: 'Close' },
 | 
				
			||||||
| 
						 | 
					@ -169,10 +169,8 @@ class MediaModal extends ImmutablePureComponent {
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
      } else if (image.get('type') === 'gifv') {
 | 
					      } else if (image.get('type') === 'gifv') {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
          <ExtendedVideoPlayer
 | 
					          <GIFV
 | 
				
			||||||
            src={image.get('url')}
 | 
					            src={image.get('url')}
 | 
				
			||||||
            muted
 | 
					 | 
				
			||||||
            controls={false}
 | 
					 | 
				
			||||||
            width={width}
 | 
					            width={width}
 | 
				
			||||||
            height={height}
 | 
					            height={height}
 | 
				
			||||||
            key={image.get('preview_url')}
 | 
					            key={image.get('preview_url')}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3127,37 +3127,27 @@ a.status-card.compact:hover {
 | 
				
			||||||
  cursor: default;
 | 
					  cursor: default;
 | 
				
			||||||
  display: flex;
 | 
					  display: flex;
 | 
				
			||||||
  flex: 1 1 auto;
 | 
					  flex: 1 1 auto;
 | 
				
			||||||
 | 
					  flex-direction: column;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  padding: 20px;
 | 
					  padding: 20px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  & > div {
 | 
					 | 
				
			||||||
    width: 100%;
 | 
					 | 
				
			||||||
    background: transparent;
 | 
					 | 
				
			||||||
    padding-top: 0;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  &__figure {
 | 
					  &__figure {
 | 
				
			||||||
    background: url('~images/elephant_ui_working.svg') no-repeat center 0;
 | 
					    &,
 | 
				
			||||||
    width: 100%;
 | 
					    img {
 | 
				
			||||||
    height: 160px;
 | 
					      display: block;
 | 
				
			||||||
    background-size: contain;
 | 
					      width: auto;
 | 
				
			||||||
    position: absolute;
 | 
					      height: 160px;
 | 
				
			||||||
    top: 50%;
 | 
					      margin: 0;
 | 
				
			||||||
    left: 50%;
 | 
					 | 
				
			||||||
    transform: translate(-50%, -50%);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  &.missing-indicator {
 | 
					 | 
				
			||||||
    padding-top: 20px + 48px;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    .regeneration-indicator__figure {
 | 
					 | 
				
			||||||
      background-image: url('~images/elephant_ui_disappointed.svg');
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  &--without-header {
 | 
				
			||||||
 | 
					    padding-top: 20px + 48px;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &__label {
 | 
					  &__label {
 | 
				
			||||||
    margin-top: 200px;
 | 
					    margin-top: 30px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    strong {
 | 
					    strong {
 | 
				
			||||||
      display: block;
 | 
					      display: block;
 | 
				
			||||||
| 
						 | 
					@ -6102,7 +6092,8 @@ noscript {
 | 
				
			||||||
  background: $base-shadow-color;
 | 
					  background: $base-shadow-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  img,
 | 
					  img,
 | 
				
			||||||
  video {
 | 
					  video,
 | 
				
			||||||
 | 
					  canvas {
 | 
				
			||||||
    display: block;
 | 
					    display: block;
 | 
				
			||||||
    max-height: 80vh;
 | 
					    max-height: 80vh;
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,10 @@
 | 
				
			||||||
  flex-direction: column;
 | 
					  flex-direction: column;
 | 
				
			||||||
  justify-content: center;
 | 
					  justify-content: center;
 | 
				
			||||||
  align-items: center;
 | 
					  align-items: center;
 | 
				
			||||||
 | 
					  height: 100vh;
 | 
				
			||||||
 | 
					  background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @media screen and (max-width: 920px) {
 | 
					  @media screen and (max-width: 920px) {
 | 
				
			||||||
    background: darken($ui-base-color, 8%);
 | 
					 | 
				
			||||||
    display: block !important;
 | 
					    display: block !important;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -19,7 +19,7 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def filter?(timeline_type, status, receiver_id)
 | 
					  def filter?(timeline_type, status, receiver_id)
 | 
				
			||||||
    if timeline_type == :home
 | 
					    if timeline_type == :home
 | 
				
			||||||
      filter_from_home?(status, receiver_id)
 | 
					      filter_from_home?(status, receiver_id, build_crutches(receiver_id, [status]))
 | 
				
			||||||
    elsif timeline_type == :mentions
 | 
					    elsif timeline_type == :mentions
 | 
				
			||||||
      filter_from_mentions?(status, receiver_id)
 | 
					      filter_from_mentions?(status, receiver_id)
 | 
				
			||||||
    elsif timeline_type == :direct
 | 
					    elsif timeline_type == :direct
 | 
				
			||||||
| 
						 | 
					@ -31,6 +31,7 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def push_to_home(account, status)
 | 
					  def push_to_home(account, status)
 | 
				
			||||||
    return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
 | 
					    return false unless add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trim(:home, account.id)
 | 
					    trim(:home, account.id)
 | 
				
			||||||
    PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
 | 
					    PushUpdateWorker.perform_async(account.id, status.id, "timeline:#{account.id}") if push_update_required?("timeline:#{account.id}")
 | 
				
			||||||
    true
 | 
					    true
 | 
				
			||||||
| 
						 | 
					@ -38,6 +39,7 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def unpush_from_home(account, status)
 | 
					  def unpush_from_home(account, status)
 | 
				
			||||||
    return false unless remove_from_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
 | 
					    return false unless remove_from_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
 | 
					    redis.publish("timeline:#{account.id}", Oj.dump(event: :delete, payload: status.id.to_s))
 | 
				
			||||||
    true
 | 
					    true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -49,7 +51,9 @@ class FeedManager
 | 
				
			||||||
      should_filter &&= !(list.show_list_replies? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
 | 
					      should_filter &&= !(list.show_list_replies? && ListAccount.where(list_id: list.id, account_id: status.in_reply_to_account_id).exists?)
 | 
				
			||||||
      return false if should_filter
 | 
					      return false if should_filter
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
 | 
					    return false unless add_to_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trim(:list, list.id)
 | 
					    trim(:list, list.id)
 | 
				
			||||||
    PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
 | 
					    PushUpdateWorker.perform_async(list.account_id, status.id, "timeline:list:#{list.id}") if push_update_required?("timeline:list:#{list.id}")
 | 
				
			||||||
    true
 | 
					    true
 | 
				
			||||||
| 
						 | 
					@ -57,6 +61,7 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def unpush_from_list(list, status)
 | 
					  def unpush_from_list(list, status)
 | 
				
			||||||
    return false unless remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
 | 
					    return false unless remove_from_feed(:list, list.id, status, list.account.user&.aggregates_reblogs?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
 | 
					    redis.publish("timeline:list:#{list.id}", Oj.dump(event: :delete, payload: status.id.to_s))
 | 
				
			||||||
    true
 | 
					    true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -100,16 +105,21 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def merge_into_timeline(from_account, into_account)
 | 
					  def merge_into_timeline(from_account, into_account)
 | 
				
			||||||
    timeline_key = key(:home, into_account.id)
 | 
					    timeline_key = key(:home, into_account.id)
 | 
				
			||||||
    query        = from_account.statuses.limit(FeedManager::MAX_ITEMS / 4)
 | 
					    aggregate    = into_account.user&.aggregates_reblogs?
 | 
				
			||||||
 | 
					    query        = from_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(FeedManager::MAX_ITEMS / 4)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
 | 
					    if redis.zcard(timeline_key) >= FeedManager::MAX_ITEMS / 4
 | 
				
			||||||
      oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true)&.first&.last&.to_i || 0
 | 
					      oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
 | 
				
			||||||
      query = query.where('id > ?', oldest_home_score)
 | 
					      query = query.where('id > ?', oldest_home_score)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    query.each do |status|
 | 
					    statuses = query.to_a
 | 
				
			||||||
      next if status.direct_visibility? || status.limited_visibility? || filter?(:home, status, into_account)
 | 
					    crutches = build_crutches(into_account.id, statuses)
 | 
				
			||||||
      add_to_feed(:home, into_account.id, status, into_account.user&.aggregates_reblogs?)
 | 
					
 | 
				
			||||||
 | 
					    statuses.each do |status|
 | 
				
			||||||
 | 
					      next if filter_from_home?(status, into_account, crutches)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      add_to_feed(:home, into_account.id, status, aggregate)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    trim(:home, into_account.id)
 | 
					    trim(:home, into_account.id)
 | 
				
			||||||
| 
						 | 
					@ -135,24 +145,35 @@ class FeedManager
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def populate_feed(account)
 | 
					  def populate_feed(account)
 | 
				
			||||||
    added  = 0
 | 
					    limit        = FeedManager::MAX_ITEMS / 2
 | 
				
			||||||
    limit  = FeedManager::MAX_ITEMS / 2
 | 
					    aggregate    = account.user&.aggregates_reblogs?
 | 
				
			||||||
    max_id = nil
 | 
					    timeline_key = key(:home, account.id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    loop do
 | 
					    account.statuses.where.not(visibility: :direct).limit(limit).each do |status|
 | 
				
			||||||
      statuses = Status.as_home_timeline(account)
 | 
					      add_to_feed(:home, account.id, status, aggregate)
 | 
				
			||||||
                       .paginate_by_max_id(limit, max_id)
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      break if statuses.empty?
 | 
					    account.following.includes(:account_stat).find_each do |target_account|
 | 
				
			||||||
 | 
					      if redis.zcard(timeline_key) >= limit
 | 
				
			||||||
 | 
					        oldest_home_score = redis.zrange(timeline_key, 0, 0, with_scores: true).first.last.to_i
 | 
				
			||||||
 | 
					        last_status_score = Mastodon::Snowflake.id_at(account.last_status_at)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      statuses.each do |status|
 | 
					        # If the feed is full and this account has not posted more recently
 | 
				
			||||||
        next if filter_from_home?(status, account)
 | 
					        # than the last item on the feed, then we can skip the whole account
 | 
				
			||||||
        added += 1 if add_to_feed(:home, account.id, status, account.user&.aggregates_reblogs?)
 | 
					        # because none of its statuses would stay on the feed anyway
 | 
				
			||||||
 | 
					        next if last_status_score < oldest_home_score
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      break unless added.zero?
 | 
					      statuses = target_account.statuses.where(visibility: [:public, :unlisted, :private]).includes(:preloadable_poll, reblog: :account).limit(limit)
 | 
				
			||||||
 | 
					      crutches = build_crutches(account.id, statuses)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      max_id = statuses.last.id
 | 
					      statuses.each do |status|
 | 
				
			||||||
 | 
					        next if filter_from_home?(status, account, crutches)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        add_to_feed(:home, account.id, status, aggregate)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      trim(:home, account.id)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -188,31 +209,33 @@ class FeedManager
 | 
				
			||||||
      (context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?)
 | 
					      (context == :home ? Mute.where(account_id: receiver_id, target_account_id: account_ids).any? : Mute.where(account_id: receiver_id, target_account_id: account_ids, hide_notifications: true).any?)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def filter_from_home?(status, receiver_id)
 | 
					  def filter_from_home?(status, receiver_id, crutches)
 | 
				
			||||||
    return false if receiver_id == status.account_id
 | 
					    return false if receiver_id == status.account_id
 | 
				
			||||||
    return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
 | 
					    return true  if status.reply? && (status.in_reply_to_id.nil? || status.in_reply_to_account_id.nil?)
 | 
				
			||||||
    return true  if phrase_filtered?(status, receiver_id, :home)
 | 
					    return true  if phrase_filtered?(status, receiver_id, :home)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    check_for_blocks = status.active_mentions.pluck(:account_id)
 | 
					    check_for_blocks = crutches[:active_mentions][status.id] || []
 | 
				
			||||||
    check_for_blocks.concat([status.account_id])
 | 
					    check_for_blocks.concat([status.account_id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if status.reblog?
 | 
					    if status.reblog?
 | 
				
			||||||
      check_for_blocks.concat([status.reblog.account_id])
 | 
					      check_for_blocks.concat([status.reblog.account_id])
 | 
				
			||||||
      check_for_blocks.concat(status.reblog.active_mentions.pluck(:account_id))
 | 
					      check_for_blocks.concat(crutches[:active_mentions][status.reblog_of_id] || [])
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true if blocks_or_mutes?(receiver_id, check_for_blocks, :home)
 | 
					    return true if check_for_blocks.any? { |target_account_id| crutches[:blocking][target_account_id] || crutches[:muting][target_account_id] }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if status.reply? && !status.in_reply_to_account_id.nil?                                                                      # Filter out if it's a reply
 | 
					    if status.reply? && !status.in_reply_to_account_id.nil?                                                                      # Filter out if it's a reply
 | 
				
			||||||
      should_filter   = !Follow.where(account_id: receiver_id, target_account_id: status.in_reply_to_account_id).exists?         # and I'm not following the person it's a reply to
 | 
					      should_filter   = !crutches[:following][status.in_reply_to_account_id]                                                     # and I'm not following the person it's a reply to
 | 
				
			||||||
      should_filter &&= receiver_id != status.in_reply_to_account_id                                                             # and it's not a reply to me
 | 
					      should_filter &&= receiver_id != status.in_reply_to_account_id                                                             # and it's not a reply to me
 | 
				
			||||||
      should_filter &&= status.account_id != status.in_reply_to_account_id                                                       # and it's not a self-reply
 | 
					      should_filter &&= status.account_id != status.in_reply_to_account_id                                                       # and it's not a self-reply
 | 
				
			||||||
      return should_filter
 | 
					
 | 
				
			||||||
 | 
					      return !!should_filter
 | 
				
			||||||
    elsif status.reblog?                                                                                                         # Filter out a reblog
 | 
					    elsif status.reblog?                                                                                                         # Filter out a reblog
 | 
				
			||||||
      should_filter   = Follow.where(account_id: receiver_id, target_account_id: status.account_id, show_reblogs: false).exists? # if the reblogger's reblogs are suppressed
 | 
					      should_filter   = crutches[:hiding_reblogs][status.account_id]                                                             # if the reblogger's reblogs are suppressed
 | 
				
			||||||
      should_filter ||= Block.where(account_id: status.reblog.account_id, target_account_id: receiver_id).exists?                # or if the author of the reblogged status is blocking me
 | 
					      should_filter ||= crutches[:blocked_by][status.reblog.account_id]                                                          # or if the author of the reblogged status is blocking me
 | 
				
			||||||
      should_filter ||= AccountDomainBlock.where(account_id: receiver_id, domain: status.reblog.account.domain).exists?          # or the author's domain is blocked
 | 
					      should_filter ||= crutches[:domain_blocking][status.reblog.account.domain]                                                 # or the author's domain is blocked
 | 
				
			||||||
      return should_filter
 | 
					
 | 
				
			||||||
 | 
					      return !!should_filter
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    false
 | 
					    false
 | 
				
			||||||
| 
						 | 
					@ -349,4 +372,31 @@ class FeedManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    redis.zrem(timeline_key, status.id)
 | 
					    redis.zrem(timeline_key, status.id)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def build_crutches(receiver_id, statuses)
 | 
				
			||||||
 | 
					    crutches = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    crutches[:active_mentions] = Mention.active.where(status_id: statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact).pluck(:status_id, :account_id).each_with_object({}) { |(id, account_id), mapping| (mapping[id] ||= []).push(account_id) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    check_for_blocks = statuses.flat_map do |s|
 | 
				
			||||||
 | 
					      arr = crutches[:active_mentions][s.id] || []
 | 
				
			||||||
 | 
					      arr.concat([s.account_id])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if s.reblog?
 | 
				
			||||||
 | 
					        arr.concat([s.reblog.account_id])
 | 
				
			||||||
 | 
					        arr.concat(crutches[:active_mentions][s.reblog_of_id] || [])
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      arr
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    crutches[:following]       = Follow.where(account_id: receiver_id, target_account_id: statuses.map(&:in_reply_to_account_id).compact).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
 | 
				
			||||||
 | 
					    crutches[:hiding_reblogs]  = Follow.where(account_id: receiver_id, target_account_id: statuses.map { |s| s.account_id if s.reblog? }.compact, show_reblogs: false).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
 | 
				
			||||||
 | 
					    crutches[:blocking]        = Block.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
 | 
				
			||||||
 | 
					    crutches[:muting]          = Mute.where(account_id: receiver_id, target_account_id: check_for_blocks).pluck(:target_account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
 | 
				
			||||||
 | 
					    crutches[:domain_blocking] = AccountDomainBlock.where(account_id: receiver_id, domain: statuses.map { |s| s.reblog&.account&.domain }.compact).pluck(:domain).each_with_object({}) { |domain, mapping| mapping[domain] = true }
 | 
				
			||||||
 | 
					    crutches[:blocked_by]      = Block.where(target_account_id: receiver_id, account_id: statuses.map { |s| s.reblog&.account_id }.compact).pluck(:account_id).each_with_object({}) { |id, mapping| mapping[id] = true }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    crutches
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,6 @@ class SpamCheck
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def flag!
 | 
					  def flag!
 | 
				
			||||||
    auto_silence_account!
 | 
					 | 
				
			||||||
    auto_report_status!
 | 
					    auto_report_status!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -134,17 +133,13 @@ class SpamCheck
 | 
				
			||||||
    text.gsub(/\s+/, ' ').strip
 | 
					    text.gsub(/\s+/, ' ').strip
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def auto_silence_account!
 | 
					 | 
				
			||||||
    @account.silence!
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def auto_report_status!
 | 
					  def auto_report_status!
 | 
				
			||||||
    status_ids = Status.where(visibility: %i(public unlisted)).where(id: matching_status_ids).pluck(:id) + [@status.id] if @status.distributable?
 | 
					    status_ids = Status.where(visibility: %i(public unlisted)).where(id: matching_status_ids).pluck(:id) + [@status.id] if @status.distributable?
 | 
				
			||||||
    ReportService.new.call(Account.representative, @account, status_ids: status_ids, comment: I18n.t('spam_check.spam_detected_and_silenced'))
 | 
					    ReportService.new.call(Account.representative, @account, status_ids: status_ids, comment: I18n.t('spam_check.spam_detected_and_silenced'))
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def already_flagged?
 | 
					  def already_flagged?
 | 
				
			||||||
    @account.silenced?
 | 
					    @account.silenced? || @account.targeted_reports.unresolved.where(account_id: -99).exists?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def trusted?
 | 
					  def trusted?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -202,7 +202,7 @@ class Account < ApplicationRecord
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def unsilence!
 | 
					  def unsilence!
 | 
				
			||||||
    update!(silenced_at: nil, trust_level: trust_level == TRUST_LEVELS[:untrusted] ? TRUST_LEVELS[:trusted] : trust_level)
 | 
					    update!(silenced_at: nil)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def suspended?
 | 
					  def suspended?
 | 
				
			||||||
| 
						 | 
					@ -312,10 +312,9 @@ class Account < ApplicationRecord
 | 
				
			||||||
  def save_with_optional_media!
 | 
					  def save_with_optional_media!
 | 
				
			||||||
    save!
 | 
					    save!
 | 
				
			||||||
  rescue ActiveRecord::RecordInvalid
 | 
					  rescue ActiveRecord::RecordInvalid
 | 
				
			||||||
    self.avatar              = nil
 | 
					    self.avatar = nil
 | 
				
			||||||
    self.header              = nil
 | 
					    self.header = nil
 | 
				
			||||||
    self[:avatar_remote_url] = ''
 | 
					
 | 
				
			||||||
    self[:header_remote_url] = ''
 | 
					 | 
				
			||||||
    save!
 | 
					    save!
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -62,6 +62,8 @@ class Admin::AccountAction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_action!
 | 
					  def process_action!
 | 
				
			||||||
    case type
 | 
					    case type
 | 
				
			||||||
 | 
					    when 'none'
 | 
				
			||||||
 | 
					      handle_resolve!
 | 
				
			||||||
    when 'disable'
 | 
					    when 'disable'
 | 
				
			||||||
      handle_disable!
 | 
					      handle_disable!
 | 
				
			||||||
    when 'silence'
 | 
					    when 'silence'
 | 
				
			||||||
| 
						 | 
					@ -103,6 +105,16 @@ class Admin::AccountAction
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def handle_resolve!
 | 
				
			||||||
 | 
					    if with_report? && report.account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
 | 
				
			||||||
 | 
					      # This is an automated report and it is being dismissed, so it's
 | 
				
			||||||
 | 
					      # a false positive, in which case update the account's trust level
 | 
				
			||||||
 | 
					      # to prevent further spam checks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def handle_disable!
 | 
					  def handle_disable!
 | 
				
			||||||
    authorize(target_account.user, :disable?)
 | 
					    authorize(target_account.user, :disable?)
 | 
				
			||||||
    log_action(:disable, target_account.user)
 | 
					    log_action(:disable, target_account.user)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@ module Remotable
 | 
				
			||||||
          return
 | 
					          return
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || self[attribute_name] == url
 | 
					        return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank? || (self[attribute_name] == url && send("#{attachment_name}_file_name").present?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        begin
 | 
					        begin
 | 
				
			||||||
          Request.new(:get, url).perform do |response|
 | 
					          Request.new(:get, url).perform do |response|
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -36,6 +36,7 @@ class Form::AdminSettings
 | 
				
			||||||
    show_replies_in_public_timelines
 | 
					    show_replies_in_public_timelines
 | 
				
			||||||
    spam_check_enabled
 | 
					    spam_check_enabled
 | 
				
			||||||
    trends
 | 
					    trends
 | 
				
			||||||
 | 
					    trendable_by_default
 | 
				
			||||||
    show_domain_blocks
 | 
					    show_domain_blocks
 | 
				
			||||||
    show_domain_blocks_rationale
 | 
					    show_domain_blocks_rationale
 | 
				
			||||||
    noindex
 | 
					    noindex
 | 
				
			||||||
| 
						 | 
					@ -56,6 +57,7 @@ class Form::AdminSettings
 | 
				
			||||||
    show_replies_in_public_timelines
 | 
					    show_replies_in_public_timelines
 | 
				
			||||||
    spam_check_enabled
 | 
					    spam_check_enabled
 | 
				
			||||||
    trends
 | 
					    trends
 | 
				
			||||||
 | 
					    trendable_by_default
 | 
				
			||||||
    noindex
 | 
					    noindex
 | 
				
			||||||
  ).freeze
 | 
					  ).freeze
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,19 +7,7 @@ class HomeFeed < Feed
 | 
				
			||||||
    @account = account
 | 
					    @account = account
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def get(limit, max_id = nil, since_id = nil, min_id = nil)
 | 
					  def regenerating?
 | 
				
			||||||
    if redis.exists("account:#{@account.id}:regeneration")
 | 
					    redis.exists("account:#{@id}:regeneration")
 | 
				
			||||||
      from_database(limit, max_id, since_id, min_id)
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
      super
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def from_database(limit, max_id, since_id, min_id)
 | 
					 | 
				
			||||||
    Status.as_home_timeline(@account)
 | 
					 | 
				
			||||||
          .paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
 | 
					 | 
				
			||||||
          .reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
 | 
					 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -57,6 +57,7 @@ class MediaAttachment < ApplicationRecord
 | 
				
			||||||
    small: {
 | 
					    small: {
 | 
				
			||||||
      convert_options: {
 | 
					      convert_options: {
 | 
				
			||||||
        output: {
 | 
					        output: {
 | 
				
			||||||
 | 
					          'loglevel' => 'fatal',
 | 
				
			||||||
          vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
 | 
					          vf: 'scale=\'min(400\, iw):min(400\, ih)\':force_original_aspect_ratio=decrease',
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					@ -70,6 +71,7 @@ class MediaAttachment < ApplicationRecord
 | 
				
			||||||
      keep_same_format: true,
 | 
					      keep_same_format: true,
 | 
				
			||||||
      convert_options: {
 | 
					      convert_options: {
 | 
				
			||||||
        output: {
 | 
					        output: {
 | 
				
			||||||
 | 
					          'loglevel' => 'fatal',
 | 
				
			||||||
          'map_metadata' => '-1',
 | 
					          'map_metadata' => '-1',
 | 
				
			||||||
          'c:v' => 'copy',
 | 
					          'c:v' => 'copy',
 | 
				
			||||||
          'c:a' => 'copy',
 | 
					          'c:a' => 'copy',
 | 
				
			||||||
| 
						 | 
					@ -84,6 +86,7 @@ class MediaAttachment < ApplicationRecord
 | 
				
			||||||
      content_type: 'audio/mpeg',
 | 
					      content_type: 'audio/mpeg',
 | 
				
			||||||
      convert_options: {
 | 
					      convert_options: {
 | 
				
			||||||
        output: {
 | 
					        output: {
 | 
				
			||||||
 | 
					          'loglevel' => 'fatal',
 | 
				
			||||||
          'q:a' => 2,
 | 
					          'q:a' => 2,
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -291,10 +291,6 @@ class Status < ApplicationRecord
 | 
				
			||||||
      where(language: nil).or where(language: account.chosen_languages)
 | 
					      where(language: nil).or where(language: account.chosen_languages)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def as_home_timeline(account)
 | 
					 | 
				
			||||||
      where(account: [account] + account.following).where(visibility: [:public, :unlisted, :private])
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
 | 
					    def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
 | 
				
			||||||
      # direct timeline is mix of direct message from_me and to_me.
 | 
					      # direct timeline is mix of direct message from_me and to_me.
 | 
				
			||||||
      # 2 queries are executed with pagination.
 | 
					      # 2 queries are executed with pagination.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,6 +37,7 @@ class Tag < ApplicationRecord
 | 
				
			||||||
  scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
 | 
					  scope :pending_review, -> { unreviewed.where.not(requested_review_at: nil) }
 | 
				
			||||||
  scope :usable, -> { where(usable: [true, nil]) }
 | 
					  scope :usable, -> { where(usable: [true, nil]) }
 | 
				
			||||||
  scope :listable, -> { where(listable: [true, nil]) }
 | 
					  scope :listable, -> { where(listable: [true, nil]) }
 | 
				
			||||||
 | 
					  scope :trendable, -> { Setting.trendable_by_default ? where(trendable: [true, nil]) : where(trendable: true) }
 | 
				
			||||||
  scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
 | 
					  scope :discoverable, -> { listable.joins(:account_tag_stat).where(AccountTagStat.arel_table[:accounts_count].gt(0)).order(Arel.sql('account_tag_stats.accounts_count desc')) }
 | 
				
			||||||
  scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
 | 
					  scope :most_used, ->(account) { joins(:statuses).where(statuses: { account: account }).group(:id).order(Arel.sql('count(*) desc')) }
 | 
				
			||||||
  scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
 | 
					  scope :matches_name, ->(value) { where(arel_table[:name].matches("#{value}%")) }
 | 
				
			||||||
| 
						 | 
					@ -76,7 +77,7 @@ class Tag < ApplicationRecord
 | 
				
			||||||
  alias listable? listable
 | 
					  alias listable? listable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def trendable
 | 
					  def trendable
 | 
				
			||||||
    boolean_with_default('trendable', false)
 | 
					    boolean_with_default('trendable', Setting.trendable_by_default)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  alias trendable? trendable
 | 
					  alias trendable? trendable
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,7 +90,7 @@ class TrendingTags
 | 
				
			||||||
      tag_ids = redis.zrevrange(KEY, 0, LIMIT - 1).map(&:to_i)
 | 
					      tag_ids = redis.zrevrange(KEY, 0, LIMIT - 1).map(&:to_i)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      tags = Tag.where(id: tag_ids)
 | 
					      tags = Tag.where(id: tag_ids)
 | 
				
			||||||
      tags = tags.where(trendable: true) if filtered
 | 
					      tags = tags.trendable if filtered
 | 
				
			||||||
      tags = tags.each_with_object({}) { |tag, h| h[tag.id] = tag }
 | 
					      tags = tags.each_with_object({}) { |tag, h| h[tag.id] = tag }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      tag_ids.map { |tag_id| tags[tag_id] }.compact.take(limit)
 | 
					      tag_ids.map { |tag_id| tags[tag_id] }.compact.take(limit)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,8 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HashtagQueryService < BaseService
 | 
					class HashtagQueryService < BaseService
 | 
				
			||||||
 | 
					  LIMIT_PER_MODE = 4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def call(tag, params, account = nil, local = false)
 | 
					  def call(tag, params, account = nil, local = false)
 | 
				
			||||||
    tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
 | 
					    tags = tags_for(Array(tag.name) | Array(params[:any])).pluck(:id)
 | 
				
			||||||
    all  = tags_for(params[:all])
 | 
					    all  = tags_for(params[:all])
 | 
				
			||||||
| 
						 | 
					@ -15,6 +17,6 @@ class HashtagQueryService < BaseService
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def tags_for(names)
 | 
					  def tags_for(names)
 | 
				
			||||||
    Tag.matching_name(names) if names.presence
 | 
					    Tag.matching_name(Array(names).take(LIMIT_PER_MODE)) if names.present?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,13 +52,12 @@
 | 
				
			||||||
        .hero-widget__img
 | 
					        .hero-widget__img
 | 
				
			||||||
          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 | 
					          = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        - if @instance_presenter.site_short_description.present?
 | 
					        .hero-widget__text
 | 
				
			||||||
          .hero-widget__text
 | 
					          %p
 | 
				
			||||||
            %p
 | 
					            = @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
 | 
				
			||||||
              = @instance_presenter.site_short_description.html_safe.presence
 | 
					            = link_to about_more_path do
 | 
				
			||||||
              = link_to about_more_path do
 | 
					              = t('about.learn_more')
 | 
				
			||||||
                = t('about.learn_more')
 | 
					              = fa_icon 'angle-double-right'
 | 
				
			||||||
                = fa_icon 'angle-double-right'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        .hero-widget__footer
 | 
					        .hero-widget__footer
 | 
				
			||||||
          .hero-widget__footer__column
 | 
					          .hero-widget__footer__column
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,10 @@
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = custom_emoji.domain
 | 
					        = custom_emoji.domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        - if custom_emoji.local_counterpart.present?
 | 
				
			||||||
 | 
					          •
 | 
				
			||||||
 | 
					          = t('admin.accounts.location.local')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      %br/
 | 
					      %br/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - if custom_emoji.disabled?
 | 
					      - if custom_emoji.disabled?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,10 +20,10 @@
 | 
				
			||||||
      = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
 | 
					      = f.input :site_contact_email, wrapper: :with_label, label: t('admin.settings.contact_information.email')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .fields-group
 | 
					  .fields-group
 | 
				
			||||||
    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 4 }
 | 
					    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .fields-group
 | 
					  .fields-group
 | 
				
			||||||
    = f.input :site_short_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_short_description.title'), hint: t('admin.settings.site_short_description.desc_html'), input_html: { rows: 2 }
 | 
					    = f.input :site_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description.title'), hint: t('admin.settings.site_description.desc_html'), input_html: { rows: 2 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .fields-row
 | 
					  .fields-row
 | 
				
			||||||
    .fields-row__column.fields-row__column-6.fields-group
 | 
					    .fields-row__column.fields-row__column-6.fields-group
 | 
				
			||||||
| 
						 | 
					@ -71,6 +71,9 @@
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
 | 
					      = f.input :trends, as: :boolean, wrapper: :with_label, label: t('admin.settings.trends.title'), hint: t('admin.settings.trends.desc_html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .fields-group
 | 
				
			||||||
 | 
					      = f.input :trendable_by_default, as: :boolean, wrapper: :with_label, label: t('admin.settings.trendable_by_default.title'), hint: t('admin.settings.trendable_by_default.desc_html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    .fields-group
 | 
					    .fields-group
 | 
				
			||||||
      = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
 | 
					      = f.input :noindex, as: :boolean, wrapper: :with_label, label: t('admin.settings.default_noindex.title'), hint: t('admin.settings.default_noindex.desc_html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -101,8 +104,8 @@
 | 
				
			||||||
      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 | 
					      = f.input :show_domain_blocks_rationale, wrapper: :with_label, collection: %i(disabled users all), label: t('admin.settings.domain_blocks_rationale.title'), label_method: lambda { |value| t("admin.settings.domain_blocks.#{value}") }, include_blank: false, collection_wrapper_tag: 'ul', item_wrapper_tag: 'li'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .fields-group
 | 
					  .fields-group
 | 
				
			||||||
    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
 | 
					 | 
				
			||||||
    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
 | 
					    = f.input :site_extended_description, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_description_extended.title'), hint: t('admin.settings.site_description_extended.desc_html'), input_html: { rows: 8 } unless whitelist_mode?
 | 
				
			||||||
 | 
					    = f.input :closed_registrations_message, as: :text, wrapper: :with_block_label, label: t('admin.settings.registrations.closed_message.title'), hint: t('admin.settings.registrations.closed_message.desc_html'), input_html: { rows: 8 }
 | 
				
			||||||
    = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
 | 
					    = f.input :site_terms, wrapper: :with_block_label, as: :text, label: t('admin.settings.site_terms.title'), hint: t('admin.settings.site_terms.desc_html'), input_html: { rows: 8 }
 | 
				
			||||||
    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
 | 
					    = f.input :custom_css, wrapper: :with_block_label, as: :text, input_html: { rows: 8 }, label: t('admin.settings.custom_css.title'), hint: t('admin.settings.custom_css.desc_html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
    = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 | 
					    = image_tag @instance_presenter.hero&.file&.url || @instance_presenter.thumbnail&.file&.url || asset_pack_path('media/images/preview.jpg'), alt: @instance_presenter.site_title
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .hero-widget__text
 | 
					  .hero-widget__text
 | 
				
			||||||
    %p= @instance_presenter.site_short_description.html_safe.presence || @instance_presenter.site_description.html_safe.presence || t('about.generic_description', domain: site_hostname)
 | 
					    %p= @instance_presenter.site_short_description.html_safe.presence || t('about.about_mastodon_html')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- if Setting.trends && !(user_signed_in? && !current_user.setting_trends)
 | 
					- if Setting.trends && !(user_signed_in? && !current_user.setting_trends)
 | 
				
			||||||
  - trends = TrendingTags.get(3)
 | 
					  - trends = TrendingTags.get(3)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
- thumbnail     = @instance_presenter.thumbnail
 | 
					- thumbnail     = @instance_presenter.thumbnail
 | 
				
			||||||
- description ||= strip_tags(@instance_presenter.site_short_description.presence || @instance_presenter.site_description.presence || t('about.about_mastodon_html'))
 | 
					- description ||= strip_tags(@instance_presenter.site_short_description.presence || t('about.about_mastodon_html'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
%meta{ name: 'description', content: description }/
 | 
					%meta{ name: 'description', content: description }/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@
 | 
				
			||||||
      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
 | 
					      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
 | 
				
			||||||
        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} 
 | 
					        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} 
 | 
				
			||||||
        %button.status__content__spoiler-link= t('statuses.show_more')
 | 
					        %button.status__content__spoiler-link= t('statuses.show_more')
 | 
				
			||||||
    .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }
 | 
					    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@
 | 
				
			||||||
      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
 | 
					      %p{ :style => ('margin-bottom: 0' unless current_account&.user&.setting_expand_spoilers) }<
 | 
				
			||||||
        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} 
 | 
					        %span.p-summary> #{Formatter.instance.format_spoiler(status, autoplay: autoplay)} 
 | 
				
			||||||
        %button.status__content__spoiler-link= t('statuses.show_more')
 | 
					        %button.status__content__spoiler-link= t('statuses.show_more')
 | 
				
			||||||
    .e-content{ lang: status.language, style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{rtl_status?(status) ? 'rtl' : 'ltr'}" }<
 | 
					    .e-content{ style: "display: #{!current_account&.user&.setting_expand_spoilers && status.spoiler_text? ? 'none' : 'block'}; direction: #{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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lock '3.11.1'
 | 
					lock '3.11.2'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
 | 
					set :repo_url, ENV.fetch('REPO', 'https://github.com/tootsuite/mastodon.git')
 | 
				
			||||||
set :branch, ENV.fetch('BRANCH', 'master')
 | 
					set :branch, ENV.fetch('BRANCH', 'master')
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require_relative '../../lib/json_ld/security'
 | 
					require_relative '../../lib/json_ld/security'
 | 
				
			||||||
 | 
					require_relative '../../lib/json_ld/identity'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,10 +1,11 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Paperclip.options[:read_timeout] = 60
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Paperclip.interpolates :filename do |attachment, style|
 | 
					Paperclip.interpolates :filename do |attachment, style|
 | 
				
			||||||
  return attachment.original_filename if style == :original
 | 
					  if style == :original
 | 
				
			||||||
  [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
 | 
					    attachment.original_filename
 | 
				
			||||||
 | 
					  else
 | 
				
			||||||
 | 
					    [basename(attachment, style), extension(attachment, style)].delete_if(&:blank?).join('.')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Paperclip::Attachment.default_options.merge!(
 | 
					Paperclip::Attachment.default_options.merge!(
 | 
				
			||||||
| 
						 | 
					@ -24,22 +25,27 @@ if ENV['S3_ENABLED'] == 'true'
 | 
				
			||||||
    storage: :s3,
 | 
					    storage: :s3,
 | 
				
			||||||
    s3_protocol: s3_protocol,
 | 
					    s3_protocol: s3_protocol,
 | 
				
			||||||
    s3_host_name: s3_hostname,
 | 
					    s3_host_name: s3_hostname,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    s3_headers: {
 | 
					    s3_headers: {
 | 
				
			||||||
      'X-Amz-Multipart-Threshold' => ENV.fetch('S3_MULTIPART_THRESHOLD') { 15.megabytes }.to_i,
 | 
					      'X-Amz-Multipart-Threshold' => ENV.fetch('S3_MULTIPART_THRESHOLD') { 15.megabytes }.to_i,
 | 
				
			||||||
      'Cache-Control' => 'public, max-age=315576000, immutable',
 | 
					      'Cache-Control' => 'public, max-age=315576000, immutable',
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    s3_permissions: ENV.fetch('S3_PERMISSION') { 'public-read' },
 | 
					    s3_permissions: ENV.fetch('S3_PERMISSION') { 'public-read' },
 | 
				
			||||||
    s3_region: s3_region,
 | 
					    s3_region: s3_region,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    s3_credentials: {
 | 
					    s3_credentials: {
 | 
				
			||||||
      bucket: ENV['S3_BUCKET'],
 | 
					      bucket: ENV['S3_BUCKET'],
 | 
				
			||||||
      access_key_id: ENV['AWS_ACCESS_KEY_ID'],
 | 
					      access_key_id: ENV['AWS_ACCESS_KEY_ID'],
 | 
				
			||||||
      secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
 | 
					      secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    s3_options: {
 | 
					    s3_options: {
 | 
				
			||||||
      signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
 | 
					      signature_version: ENV.fetch('S3_SIGNATURE_VERSION') { 'v4' },
 | 
				
			||||||
      http_open_timeout: 5,
 | 
					      http_open_timeout: 5,
 | 
				
			||||||
      http_read_timeout: 5,
 | 
					      http_read_timeout: 5,
 | 
				
			||||||
      http_idle_timeout: 5,
 | 
					      http_idle_timeout: 5,
 | 
				
			||||||
 | 
					      retry_limit: 0,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +54,7 @@ if ENV['S3_ENABLED'] == 'true'
 | 
				
			||||||
      endpoint: ENV['S3_ENDPOINT'],
 | 
					      endpoint: ENV['S3_ENDPOINT'],
 | 
				
			||||||
      force_path_style: true
 | 
					      force_path_style: true
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Paperclip::Attachment.default_options[:url] = ':s3_path_url'
 | 
					    Paperclip::Attachment.default_options[:url] = ':s3_path_url'
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,6 +80,7 @@ elsif ENV['SWIFT_ENABLED'] == 'true'
 | 
				
			||||||
      openstack_region: ENV['SWIFT_REGION'],
 | 
					      openstack_region: ENV['SWIFT_REGION'],
 | 
				
			||||||
      openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
 | 
					      openstack_cache_ttl: ENV.fetch('SWIFT_CACHE_TTL') { 60 },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fog_directory: ENV['SWIFT_CONTAINER'],
 | 
					    fog_directory: ENV['SWIFT_CONTAINER'],
 | 
				
			||||||
    fog_host: ENV['SWIFT_OBJECT_URL'],
 | 
					    fog_host: ENV['SWIFT_OBJECT_URL'],
 | 
				
			||||||
    fog_public: true
 | 
					    fog_public: true
 | 
				
			||||||
| 
						 | 
					@ -81,7 +89,7 @@ else
 | 
				
			||||||
  Paperclip::Attachment.default_options.merge!(
 | 
					  Paperclip::Attachment.default_options.merge!(
 | 
				
			||||||
    storage: :filesystem,
 | 
					    storage: :filesystem,
 | 
				
			||||||
    use_timestamp: true,
 | 
					    use_timestamp: true,
 | 
				
			||||||
    path: (ENV['PAPERCLIP_ROOT_PATH'] || ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
 | 
					    path: ENV.fetch('PAPERCLIP_ROOT_PATH', ':rails_root/public/system') + '/:class/:attachment/:id_partition/:style/:filename',
 | 
				
			||||||
    url: (ENV['PAPERCLIP_ROOT_URL'] || '/system') + '/:class/:attachment/:id_partition/:style/:filename',
 | 
					    url: ENV.fetch('PAPERCLIP_ROOT_URL', '/system') + '/:class/:attachment/:id_partition/:style/:filename',
 | 
				
			||||||
  )
 | 
					  )
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
en:
 | 
					en:
 | 
				
			||||||
  about:
 | 
					  about:
 | 
				
			||||||
    about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
 | 
					    about_hashtag_html: These are public toots tagged with <strong>#%{hashtag}</strong>. You can interact with them if you have an account anywhere in the fediverse.
 | 
				
			||||||
    about_mastodon_html: Mastodon is a social network based on open web protocols and free, open-source software. It is decentralized like e-mail.
 | 
					    about_mastodon_html: 'The social network of the future: No ads, no corporate surveillance, ethical design, and decentralization! Own your data with Mastodon!'
 | 
				
			||||||
    about_this: About
 | 
					    about_this: About
 | 
				
			||||||
    active_count_after: active
 | 
					    active_count_after: active
 | 
				
			||||||
    active_footnote: Monthly Active Users (MAU)
 | 
					    active_footnote: Monthly Active Users (MAU)
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,6 @@ en:
 | 
				
			||||||
    discover_users: Discover users
 | 
					    discover_users: Discover users
 | 
				
			||||||
    documentation: Documentation
 | 
					    documentation: Documentation
 | 
				
			||||||
    federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
 | 
					    federation_hint_html: With an account on %{instance} you'll be able to follow people on any Mastodon server and beyond.
 | 
				
			||||||
    generic_description: "%{domain} is one server in the network"
 | 
					 | 
				
			||||||
    get_apps: Try a mobile app
 | 
					    get_apps: Try a mobile app
 | 
				
			||||||
    hosted_on: Mastodon hosted on %{domain}
 | 
					    hosted_on: Mastodon hosted on %{domain}
 | 
				
			||||||
    instance_actor_flash: |
 | 
					    instance_actor_flash: |
 | 
				
			||||||
| 
						 | 
					@ -486,8 +485,8 @@ en:
 | 
				
			||||||
          open: Anyone can sign up
 | 
					          open: Anyone can sign up
 | 
				
			||||||
        title: Registrations mode
 | 
					        title: Registrations mode
 | 
				
			||||||
      show_known_fediverse_at_about_page:
 | 
					      show_known_fediverse_at_about_page:
 | 
				
			||||||
        desc_html: When toggled, it will show toots from all the known fediverse on preview. Otherwise it will only show local toots.
 | 
					        desc_html: When disabled, restricts the public timeline linked from the landing page to showing only local content
 | 
				
			||||||
        title: Show known fediverse on timeline preview
 | 
					        title: Include federated content on unauthenticated public timeline page
 | 
				
			||||||
      show_reblogs_in_public_timelines:
 | 
					      show_reblogs_in_public_timelines:
 | 
				
			||||||
        desc_html: Show public boosts of public toots in local and public timelines.
 | 
					        desc_html: Show public boosts of public toots in local and public timelines.
 | 
				
			||||||
        title: Show boosts in public timelines
 | 
					        title: Show boosts in public timelines
 | 
				
			||||||
| 
						 | 
					@ -511,15 +510,18 @@ en:
 | 
				
			||||||
        title: Custom terms of service
 | 
					        title: Custom terms of service
 | 
				
			||||||
      site_title: Server name
 | 
					      site_title: Server name
 | 
				
			||||||
      spam_check_enabled:
 | 
					      spam_check_enabled:
 | 
				
			||||||
        desc_html: Mastodon can auto-silence and auto-report accounts that send repeated unsolicited messages. There may be false positives.
 | 
					        desc_html: Mastodon can auto-report accounts that send repeated unsolicited messages. There may be false positives.
 | 
				
			||||||
        title: Anti-spam automation
 | 
					        title: Anti-spam automation
 | 
				
			||||||
      thumbnail:
 | 
					      thumbnail:
 | 
				
			||||||
        desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
 | 
					        desc_html: Used for previews via OpenGraph and API. 1200x630px recommended
 | 
				
			||||||
        title: Server thumbnail
 | 
					        title: Server thumbnail
 | 
				
			||||||
      timeline_preview:
 | 
					      timeline_preview:
 | 
				
			||||||
        desc_html: Display public timeline on landing page
 | 
					        desc_html: Display link to public timeline on landing page and allow API access to the public timeline without authentication
 | 
				
			||||||
        title: Timeline preview
 | 
					        title: Allow unauthenticated access to public timeline
 | 
				
			||||||
      title: Site settings
 | 
					      title: Site settings
 | 
				
			||||||
 | 
					      trendable_by_default:
 | 
				
			||||||
 | 
					        desc_html: Affects hashtags that have not been previously disallowed
 | 
				
			||||||
 | 
					        title: Allow hashtags to trend without prior review
 | 
				
			||||||
      trends:
 | 
					      trends:
 | 
				
			||||||
        desc_html: Publicly display previously reviewed hashtags that are currently trending
 | 
					        desc_html: Publicly display previously reviewed hashtags that are currently trending
 | 
				
			||||||
        title: Trending hashtags
 | 
					        title: Trending hashtags
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@ defaults: &defaults
 | 
				
			||||||
  use_blurhash: true
 | 
					  use_blurhash: true
 | 
				
			||||||
  use_pending_items: false
 | 
					  use_pending_items: false
 | 
				
			||||||
  trends: true
 | 
					  trends: true
 | 
				
			||||||
 | 
					  trendable_by_default: false
 | 
				
			||||||
  notification_emails:
 | 
					  notification_emails:
 | 
				
			||||||
    follow: false
 | 
					    follow: false
 | 
				
			||||||
    reblog: false
 | 
					    reblog: false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,6 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def notifications_about_direct_statuses
 | 
					  def notifications_about_direct_statuses
 | 
				
			||||||
    Notification.joins(mention: :status).where(activity_type: 'Mention', statuses: { visibility: :direct })
 | 
					    Notification.joins('INNER JOIN mentions ON mentions.id = notifications.activity_id INNER JOIN statuses ON statuses.id = mentions.status_id').where(activity_type: 'Mention', statuses: { visibility: :direct })
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,11 @@
 | 
				
			||||||
 | 
					class UpdatePtLocales < ActiveRecord::Migration[5.2]
 | 
				
			||||||
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def up
 | 
				
			||||||
 | 
					    User.where(locale: 'pt').in_batches.update_all(locale: 'pt-PT')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def down
 | 
				
			||||||
 | 
					    User.where(locale: 'pt-PT').in_batches.update_all(locale: 'pt')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										26
									
								
								db/schema.rb
								
								
								
								
							
							
						
						
									
										26
									
								
								db/schema.rb
								
								
								
								
							| 
						 | 
					@ -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: 2019_10_01_213028) do
 | 
					ActiveRecord::Schema.define(version: 2019_10_07_013357) 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"
 | 
				
			||||||
| 
						 | 
					@ -706,6 +706,30 @@ ActiveRecord::Schema.define(version: 2019_10_01_213028) do
 | 
				
			||||||
    t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
 | 
					    t.index ["tag_id", "status_id"], name: "index_statuses_tags_on_tag_id_and_status_id", unique: true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create_table "stream_entries", force: :cascade do |t|
 | 
				
			||||||
 | 
					    t.bigint "activity_id"
 | 
				
			||||||
 | 
					    t.string "activity_type"
 | 
				
			||||||
 | 
					    t.datetime "created_at", null: false
 | 
				
			||||||
 | 
					    t.datetime "updated_at", null: false
 | 
				
			||||||
 | 
					    t.boolean "hidden", default: false, null: false
 | 
				
			||||||
 | 
					    t.bigint "account_id"
 | 
				
			||||||
 | 
					    t.index ["account_id", "activity_type", "id"], name: "index_stream_entries_on_account_id_and_activity_type_and_id"
 | 
				
			||||||
 | 
					    t.index ["activity_id", "activity_type"], name: "index_stream_entries_on_activity_id_and_activity_type"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create_table "subscriptions", force: :cascade do |t|
 | 
				
			||||||
 | 
					    t.string "callback_url", default: "", null: false
 | 
				
			||||||
 | 
					    t.string "secret"
 | 
				
			||||||
 | 
					    t.datetime "expires_at"
 | 
				
			||||||
 | 
					    t.boolean "confirmed", default: false, null: false
 | 
				
			||||||
 | 
					    t.datetime "created_at", null: false
 | 
				
			||||||
 | 
					    t.datetime "updated_at", null: false
 | 
				
			||||||
 | 
					    t.datetime "last_successful_delivery_at"
 | 
				
			||||||
 | 
					    t.string "domain"
 | 
				
			||||||
 | 
					    t.bigint "account_id", null: false
 | 
				
			||||||
 | 
					    t.index ["account_id", "callback_url"], name: "index_subscriptions_on_account_id_and_callback_url", unique: true
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create_table "tags", force: :cascade do |t|
 | 
					  create_table "tags", force: :cascade do |t|
 | 
				
			||||||
    t.string "name", default: "", null: false
 | 
					    t.string "name", default: "", null: false
 | 
				
			||||||
    t.datetime "created_at", null: false
 | 
					    t.datetime "created_at", null: false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,87 @@
 | 
				
			||||||
 | 
					# -*- encoding: utf-8 -*-
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					# This file generated automatically from http://w3id.org/identity/v1
 | 
				
			||||||
 | 
					require 'json/ld'
 | 
				
			||||||
 | 
					class JSON::LD::Context
 | 
				
			||||||
 | 
					  add_preloaded("http://w3id.org/identity/v1") do
 | 
				
			||||||
 | 
					    new(term_definitions: {
 | 
				
			||||||
 | 
					      "Credential" => TermDefinition.new("Credential", id: "https://w3id.org/credentials#Credential", simple: true),
 | 
				
			||||||
 | 
					      "CryptographicKey" => TermDefinition.new("CryptographicKey", id: "https://w3id.org/security#Key", simple: true),
 | 
				
			||||||
 | 
					      "CryptographicKeyCredential" => TermDefinition.new("CryptographicKeyCredential", id: "https://w3id.org/credentials#CryptographicKeyCredential", simple: true),
 | 
				
			||||||
 | 
					      "EncryptedMessage" => TermDefinition.new("EncryptedMessage", id: "https://w3id.org/security#EncryptedMessage", simple: true),
 | 
				
			||||||
 | 
					      "GraphSignature2012" => TermDefinition.new("GraphSignature2012", id: "https://w3id.org/security#GraphSignature2012", simple: true),
 | 
				
			||||||
 | 
					      "Group" => TermDefinition.new("Group", id: "https://www.w3.org/ns/activitystreams#Group", simple: true),
 | 
				
			||||||
 | 
					      "Identity" => TermDefinition.new("Identity", id: "https://w3id.org/identity#Identity", simple: true),
 | 
				
			||||||
 | 
					      "LinkedDataSignature2015" => TermDefinition.new("LinkedDataSignature2015", id: "https://w3id.org/security#LinkedDataSignature2015", simple: true),
 | 
				
			||||||
 | 
					      "Organization" => TermDefinition.new("Organization", id: "http://schema.org/Organization", simple: true),
 | 
				
			||||||
 | 
					      "Person" => TermDefinition.new("Person", id: "http://schema.org/Person", simple: true),
 | 
				
			||||||
 | 
					      "PostalAddress" => TermDefinition.new("PostalAddress", id: "http://schema.org/PostalAddress", simple: true),
 | 
				
			||||||
 | 
					      "about" => TermDefinition.new("about", id: "http://schema.org/about", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "accessControl" => TermDefinition.new("accessControl", id: "https://w3id.org/permissions#accessControl", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "address" => TermDefinition.new("address", id: "http://schema.org/address", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "addressCountry" => TermDefinition.new("addressCountry", id: "http://schema.org/addressCountry", simple: true),
 | 
				
			||||||
 | 
					      "addressLocality" => TermDefinition.new("addressLocality", id: "http://schema.org/addressLocality", simple: true),
 | 
				
			||||||
 | 
					      "addressRegion" => TermDefinition.new("addressRegion", id: "http://schema.org/addressRegion", simple: true),
 | 
				
			||||||
 | 
					      "cipherAlgorithm" => TermDefinition.new("cipherAlgorithm", id: "https://w3id.org/security#cipherAlgorithm", simple: true),
 | 
				
			||||||
 | 
					      "cipherData" => TermDefinition.new("cipherData", id: "https://w3id.org/security#cipherData", simple: true),
 | 
				
			||||||
 | 
					      "cipherKey" => TermDefinition.new("cipherKey", id: "https://w3id.org/security#cipherKey", simple: true),
 | 
				
			||||||
 | 
					      "claim" => TermDefinition.new("claim", id: "https://w3id.org/credentials#claim", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "comment" => TermDefinition.new("comment", id: "http://www.w3.org/2000/01/rdf-schema#comment", simple: true),
 | 
				
			||||||
 | 
					      "created" => TermDefinition.new("created", id: "http://purl.org/dc/terms/created", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
 | 
				
			||||||
 | 
					      "creator" => TermDefinition.new("creator", id: "http://purl.org/dc/terms/creator", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "cred" => TermDefinition.new("cred", id: "https://w3id.org/credentials#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "credential" => TermDefinition.new("credential", id: "https://w3id.org/credentials#credential", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "dc" => TermDefinition.new("dc", id: "http://purl.org/dc/terms/", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "description" => TermDefinition.new("description", id: "http://schema.org/description", simple: true),
 | 
				
			||||||
 | 
					      "digestAlgorithm" => TermDefinition.new("digestAlgorithm", id: "https://w3id.org/security#digestAlgorithm", simple: true),
 | 
				
			||||||
 | 
					      "digestValue" => TermDefinition.new("digestValue", id: "https://w3id.org/security#digestValue", simple: true),
 | 
				
			||||||
 | 
					      "domain" => TermDefinition.new("domain", id: "https://w3id.org/security#domain", simple: true),
 | 
				
			||||||
 | 
					      "email" => TermDefinition.new("email", id: "http://schema.org/email", simple: true),
 | 
				
			||||||
 | 
					      "expires" => TermDefinition.new("expires", id: "https://w3id.org/security#expiration", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
 | 
				
			||||||
 | 
					      "familyName" => TermDefinition.new("familyName", id: "http://schema.org/familyName", simple: true),
 | 
				
			||||||
 | 
					      "givenName" => TermDefinition.new("givenName", id: "http://schema.org/givenName", simple: true),
 | 
				
			||||||
 | 
					      "id" => TermDefinition.new("id", id: "@id", simple: true),
 | 
				
			||||||
 | 
					      "identity" => TermDefinition.new("identity", id: "https://w3id.org/identity#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "identityService" => TermDefinition.new("identityService", id: "https://w3id.org/identity#identityService", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "idp" => TermDefinition.new("idp", id: "https://w3id.org/identity#idp", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "image" => TermDefinition.new("image", id: "http://schema.org/image", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "initializationVector" => TermDefinition.new("initializationVector", id: "https://w3id.org/security#initializationVector", simple: true),
 | 
				
			||||||
 | 
					      "issued" => TermDefinition.new("issued", id: "https://w3id.org/credentials#issued", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
 | 
				
			||||||
 | 
					      "issuer" => TermDefinition.new("issuer", id: "https://w3id.org/credentials#issuer", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "label" => TermDefinition.new("label", id: "http://www.w3.org/2000/01/rdf-schema#label", simple: true),
 | 
				
			||||||
 | 
					      "member" => TermDefinition.new("member", id: "http://schema.org/member", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "memberOf" => TermDefinition.new("memberOf", id: "http://schema.org/memberOf", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "name" => TermDefinition.new("name", id: "http://schema.org/name", simple: true),
 | 
				
			||||||
 | 
					      "nonce" => TermDefinition.new("nonce", id: "https://w3id.org/security#nonce", simple: true),
 | 
				
			||||||
 | 
					      "normalizationAlgorithm" => TermDefinition.new("normalizationAlgorithm", id: "https://w3id.org/security#normalizationAlgorithm", simple: true),
 | 
				
			||||||
 | 
					      "owner" => TermDefinition.new("owner", id: "https://w3id.org/security#owner", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "password" => TermDefinition.new("password", id: "https://w3id.org/security#password", simple: true),
 | 
				
			||||||
 | 
					      "paymentProcessor" => TermDefinition.new("paymentProcessor", id: "https://w3id.org/payswarm#processor", simple: true),
 | 
				
			||||||
 | 
					      "perm" => TermDefinition.new("perm", id: "https://w3id.org/permissions#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "postalCode" => TermDefinition.new("postalCode", id: "http://schema.org/postalCode", simple: true),
 | 
				
			||||||
 | 
					      "preferences" => TermDefinition.new("preferences", id: "https://w3id.org/payswarm#preferences", type_mapping: "@vocab"),
 | 
				
			||||||
 | 
					      "privateKey" => TermDefinition.new("privateKey", id: "https://w3id.org/security#privateKey", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "privateKeyPem" => TermDefinition.new("privateKeyPem", id: "https://w3id.org/security#privateKeyPem", simple: true),
 | 
				
			||||||
 | 
					      "ps" => TermDefinition.new("ps", id: "https://w3id.org/payswarm#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "publicKey" => TermDefinition.new("publicKey", id: "https://w3id.org/security#publicKey", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "publicKeyPem" => TermDefinition.new("publicKeyPem", id: "https://w3id.org/security#publicKeyPem", simple: true),
 | 
				
			||||||
 | 
					      "publicKeyService" => TermDefinition.new("publicKeyService", id: "https://w3id.org/security#publicKeyService", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "rdf" => TermDefinition.new("rdf", id: "http://www.w3.org/1999/02/22-rdf-syntax-ns#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "rdfs" => TermDefinition.new("rdfs", id: "http://www.w3.org/2000/01/rdf-schema#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "recipient" => TermDefinition.new("recipient", id: "https://w3id.org/credentials#recipient", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "revoked" => TermDefinition.new("revoked", id: "https://w3id.org/security#revoked", type_mapping: "http://www.w3.org/2001/XMLSchema#dateTime"),
 | 
				
			||||||
 | 
					      "schema" => TermDefinition.new("schema", id: "http://schema.org/", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "sec" => TermDefinition.new("sec", id: "https://w3id.org/security#", simple: true, prefix: true),
 | 
				
			||||||
 | 
					      "signature" => TermDefinition.new("signature", id: "https://w3id.org/security#signature", simple: true),
 | 
				
			||||||
 | 
					      "signatureAlgorithm" => TermDefinition.new("signatureAlgorithm", id: "https://w3id.org/security#signatureAlgorithm", simple: true),
 | 
				
			||||||
 | 
					      "signatureValue" => TermDefinition.new("signatureValue", id: "https://w3id.org/security#signatureValue", simple: true),
 | 
				
			||||||
 | 
					      "streetAddress" => TermDefinition.new("streetAddress", id: "http://schema.org/streetAddress", simple: true),
 | 
				
			||||||
 | 
					      "title" => TermDefinition.new("title", id: "http://purl.org/dc/terms/title", simple: true),
 | 
				
			||||||
 | 
					      "type" => TermDefinition.new("type", id: "@type", simple: true),
 | 
				
			||||||
 | 
					      "url" => TermDefinition.new("url", id: "http://schema.org/url", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "writePermission" => TermDefinition.new("writePermission", id: "https://w3id.org/permissions#writePermission", type_mapping: "@id"),
 | 
				
			||||||
 | 
					      "xsd" => TermDefinition.new("xsd", id: "http://www.w3.org/2001/XMLSchema#", simple: true, prefix: true)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					  alias_preloaded("https://w3id.org/identity/v1", "http://w3id.org/identity/v1")
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -211,7 +211,6 @@ module Mastodon
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    option :concurrency, type: :numeric, default: 5, aliases: [:c]
 | 
					    option :concurrency, type: :numeric, default: 5, aliases: [:c]
 | 
				
			||||||
    option :verbose, type: :boolean, aliases: [:v]
 | 
					 | 
				
			||||||
    option :dry_run, type: :boolean
 | 
					    option :dry_run, type: :boolean
 | 
				
			||||||
    desc 'cull', 'Remove remote accounts that no longer exist'
 | 
					    desc 'cull', 'Remove remote accounts that no longer exist'
 | 
				
			||||||
    long_desc <<-LONG_DESC
 | 
					    long_desc <<-LONG_DESC
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,12 @@ module Mastodon
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def parallelize_with_progress(scope)
 | 
					    def parallelize_with_progress(scope)
 | 
				
			||||||
      ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency]
 | 
					      if options[:concurrency] < 1
 | 
				
			||||||
 | 
					        say('Cannot run with this concurrency setting, must be at least 1', :red)
 | 
				
			||||||
 | 
					        exit(1)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      ActiveRecord::Base.configurations[Rails.env]['pool'] = options[:concurrency] + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      progress  = create_progress_bar(scope.count)
 | 
					      progress  = create_progress_bar(scope.count)
 | 
				
			||||||
      pool      = Concurrent::FixedThreadPool.new(options[:concurrency])
 | 
					      pool      = Concurrent::FixedThreadPool.new(options[:concurrency])
 | 
				
			||||||
| 
						 | 
					@ -27,17 +32,26 @@ module Mastodon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        items.each do |item|
 | 
					        items.each do |item|
 | 
				
			||||||
          futures << Concurrent::Future.execute(executor: pool) do
 | 
					          futures << Concurrent::Future.execute(executor: pool) do
 | 
				
			||||||
            ActiveRecord::Base.connection_pool.with_connection do
 | 
					            begin
 | 
				
			||||||
              begin
 | 
					              if !progress.total.nil? && progress.progress + 1 > progress.total
 | 
				
			||||||
                progress.log("Processing #{item.id}") if options[:verbose]
 | 
					                # The number of items has changed between start and now,
 | 
				
			||||||
 | 
					                # since there is no good way to predict the final count from
 | 
				
			||||||
 | 
					                # here, just change the progress bar to an indeterminate one
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                result = yield(item)
 | 
					                progress.total = nil
 | 
				
			||||||
                aggregate.increment(result) if result.is_a?(Integer)
 | 
					 | 
				
			||||||
              rescue => e
 | 
					 | 
				
			||||||
                progress.log pastel.red("Error processing #{item.id}: #{e}")
 | 
					 | 
				
			||||||
              ensure
 | 
					 | 
				
			||||||
                progress.increment
 | 
					 | 
				
			||||||
              end
 | 
					              end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              progress.log("Processing #{item.id}") if options[:verbose]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              result = ActiveRecord::Base.connection_pool.with_connection do
 | 
				
			||||||
 | 
					                yield(item)
 | 
				
			||||||
 | 
					              end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					              aggregate.increment(result) if result.is_a?(Integer)
 | 
				
			||||||
 | 
					            rescue => e
 | 
				
			||||||
 | 
					              progress.log pastel.red("Error processing #{item.id}: #{e}")
 | 
				
			||||||
 | 
					            ensure
 | 
				
			||||||
 | 
					              progress.increment
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
| 
						 | 
					@ -46,7 +60,7 @@ module Mastodon
 | 
				
			||||||
        futures.map(&:value)
 | 
					        futures.map(&:value)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      progress.finish
 | 
					      progress.stop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      [total.value, aggregate.value]
 | 
					      [total.value, aggregate.value]
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,7 +27,6 @@ module Mastodon
 | 
				
			||||||
      dry_run = options[:dry_run] ? '(DRY RUN)' : ''
 | 
					      dry_run = options[:dry_run] ? '(DRY RUN)' : ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if options[:all] || username.nil?
 | 
					      if options[:all] || username.nil?
 | 
				
			||||||
 | 
					 | 
				
			||||||
        processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
 | 
					        processed, = parallelize_with_progress(Account.joins(:user).merge(User.active)) do |account|
 | 
				
			||||||
          PrecomputeFeedService.new.call(account) unless options[:dry_run]
 | 
					          PrecomputeFeedService.new.call(account) unless options[:dry_run]
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -50,6 +50,7 @@ module Mastodon
 | 
				
			||||||
    option :concurrency, type: :numeric, default: 5, aliases: [:c]
 | 
					    option :concurrency, type: :numeric, default: 5, aliases: [:c]
 | 
				
			||||||
    option :verbose, type: :boolean, default: false, aliases: [:v]
 | 
					    option :verbose, type: :boolean, default: false, aliases: [:v]
 | 
				
			||||||
    option :dry_run, type: :boolean, default: false
 | 
					    option :dry_run, type: :boolean, default: false
 | 
				
			||||||
 | 
					    option :force, type: :boolean, default: false
 | 
				
			||||||
    desc 'refresh', 'Fetch remote media files'
 | 
					    desc 'refresh', 'Fetch remote media files'
 | 
				
			||||||
    long_desc <<-DESC
 | 
					    long_desc <<-DESC
 | 
				
			||||||
      Re-downloads media attachments from other servers. You must specify the
 | 
					      Re-downloads media attachments from other servers. You must specify the
 | 
				
			||||||
| 
						 | 
					@ -62,6 +63,9 @@ module Mastodon
 | 
				
			||||||
      using username@domain handle of the account.
 | 
					      using username@domain handle of the account.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      Use the --domain option to download attachments from a specific domain.
 | 
					      Use the --domain option to download attachments from a specific domain.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      By default, attachments that are believed to be already downloaded will
 | 
				
			||||||
 | 
					      not be re-downloaded. To force re-download of every URL, use --force.
 | 
				
			||||||
    DESC
 | 
					    DESC
 | 
				
			||||||
    def refresh
 | 
					    def refresh
 | 
				
			||||||
      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
 | 
					      dry_run = options[:dry_run] ? ' (DRY RUN)' : ''
 | 
				
			||||||
| 
						 | 
					@ -85,7 +89,7 @@ module Mastodon
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      processed, aggregate = parallelize_with_progress(scope) do |media_attachment|
 | 
					      processed, aggregate = parallelize_with_progress(scope) do |media_attachment|
 | 
				
			||||||
        next if media_attachment.remote_url.blank?
 | 
					        next if media_attachment.remote_url.blank? || (!options[:force] && media_attachment.file_file_name.present?)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        unless options[:dry_run]
 | 
					        unless options[:dry_run]
 | 
				
			||||||
          media_attachment.reset_file!
 | 
					          media_attachment.reset_file!
 | 
				
			||||||
| 
						 | 
					@ -97,5 +101,17 @@ module Mastodon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
 | 
					      say("Downloaded #{processed} media attachments (approx. #{number_to_human_size(aggregate)})#{dry_run}", :green, true)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    desc 'usage', 'Calculate disk space consumed by Mastodon'
 | 
				
			||||||
 | 
					    def usage
 | 
				
			||||||
 | 
					      say("Attachments:\t#{number_to_human_size(MediaAttachment.sum(:file_file_size))} (#{number_to_human_size(MediaAttachment.where(account: Account.local).sum(:file_file_size))} local)")
 | 
				
			||||||
 | 
					      say("Custom emoji:\t#{number_to_human_size(CustomEmoji.sum(:image_file_size))} (#{number_to_human_size(CustomEmoji.local.sum(:image_file_size))} local)")
 | 
				
			||||||
 | 
					      say("Preview cards:\t#{number_to_human_size(PreviewCard.sum(:image_file_size))}")
 | 
				
			||||||
 | 
					      say("Avatars:\t#{number_to_human_size(Account.sum(:avatar_file_size))} (#{number_to_human_size(Account.local.sum(:avatar_file_size))} local)")
 | 
				
			||||||
 | 
					      say("Headers:\t#{number_to_human_size(Account.sum(:header_file_size))} (#{number_to_human_size(Account.local.sum(:header_file_size))} local)")
 | 
				
			||||||
 | 
					      say("Backups:\t#{number_to_human_size(Backup.sum(:dump_file_size))}")
 | 
				
			||||||
 | 
					      say("Imports:\t#{number_to_human_size(Import.sum(:data_file_size))}")
 | 
				
			||||||
 | 
					      say("Settings:\t#{number_to_human_size(SiteUpload.sum(:file_file_size))}")
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ module Mastodon
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def patch
 | 
					    def patch
 | 
				
			||||||
      0
 | 
					      1
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def flags
 | 
					    def flags
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -160,7 +160,7 @@
 | 
				
			||||||
    "stringz": "^2.0.0",
 | 
					    "stringz": "^2.0.0",
 | 
				
			||||||
    "substring-trie": "^1.0.2",
 | 
					    "substring-trie": "^1.0.2",
 | 
				
			||||||
    "terser-webpack-plugin": "^1.4.1",
 | 
					    "terser-webpack-plugin": "^1.4.1",
 | 
				
			||||||
    "tesseract.js": "^2.0.0-alpha.15",
 | 
					    "tesseract.js": "^2.0.0-alpha.16",
 | 
				
			||||||
    "throng": "^4.0.0",
 | 
					    "throng": "^4.0.0",
 | 
				
			||||||
    "tiny-queue": "^0.2.1",
 | 
					    "tiny-queue": "^0.2.1",
 | 
				
			||||||
    "uuid": "^3.1.0",
 | 
					    "uuid": "^3.1.0",
 | 
				
			||||||
| 
						 | 
					@ -177,7 +177,7 @@
 | 
				
			||||||
    "babel-jest": "^24.9.0",
 | 
					    "babel-jest": "^24.9.0",
 | 
				
			||||||
    "enzyme": "^3.10.0",
 | 
					    "enzyme": "^3.10.0",
 | 
				
			||||||
    "enzyme-adapter-react-16": "^1.14.0",
 | 
					    "enzyme-adapter-react-16": "^1.14.0",
 | 
				
			||||||
    "eslint": "^6.4.0",
 | 
					    "eslint": "^6.5.0",
 | 
				
			||||||
    "eslint-plugin-import": "~2.18.2",
 | 
					    "eslint-plugin-import": "~2.18.2",
 | 
				
			||||||
    "eslint-plugin-jsx-a11y": "~6.2.3",
 | 
					    "eslint-plugin-jsx-a11y": "~6.2.3",
 | 
				
			||||||
    "eslint-plugin-promise": "~4.2.1",
 | 
					    "eslint-plugin-promise": "~4.2.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -181,10 +181,6 @@ RSpec.describe SpamCheck do
 | 
				
			||||||
      described_class.new(status2).flag!
 | 
					      described_class.new(status2).flag!
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'silences the account' do
 | 
					 | 
				
			||||||
      expect(sender.silenced?).to be true
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'creates a report about the account' do
 | 
					    it 'creates a report about the account' do
 | 
				
			||||||
      expect(sender.targeted_reports.unresolved.count).to eq 1
 | 
					      expect(sender.targeted_reports.unresolved.count).to eq 1
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -126,8 +126,8 @@ RSpec.describe Account, type: :model do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
 | 
					      it 'sets default avatar, header, avatar_remote_url, and header_remote_url' do
 | 
				
			||||||
        expect(account.avatar_remote_url).to eq ''
 | 
					        expect(account.avatar_remote_url).to eq 'https://remote.test/invalid_avatar'
 | 
				
			||||||
        expect(account.header_remote_url).to eq ''
 | 
					        expect(account.header_remote_url).to eq expectation.header_remote_url
 | 
				
			||||||
        expect(account.avatar_file_name).to  eq nil
 | 
					        expect(account.avatar_file_name).to  eq nil
 | 
				
			||||||
        expect(account.header_file_name).to  eq nil
 | 
					        expect(account.header_file_name).to  eq nil
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,6 +18,8 @@ RSpec.describe Remotable do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def hoge=(arg); end
 | 
					    def hoge=(arg); end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def hoge_file_name; end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def hoge_file_name=(arg); end
 | 
					    def hoge_file_name=(arg); end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def has_attribute?(arg); end
 | 
					    def has_attribute?(arg); end
 | 
				
			||||||
| 
						 | 
					@ -109,12 +111,21 @@ RSpec.describe Remotable do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      context 'foo[attribute_name] == url' do
 | 
					      context 'foo[attribute_name] == url' do
 | 
				
			||||||
        it 'makes no request' do
 | 
					        it 'makes no request if file is saved' do
 | 
				
			||||||
          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
 | 
					          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
 | 
				
			||||||
 | 
					          allow(foo).to receive(:hoge_file_name).and_return('foo.jpg')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          foo.hoge_remote_url = url
 | 
					          foo.hoge_remote_url = url
 | 
				
			||||||
          expect(request).not_to have_been_requested
 | 
					          expect(request).not_to have_been_requested
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'makes request if file is not saved' do
 | 
				
			||||||
 | 
					          allow(foo).to receive(:[]).with(attribute_name).and_return(url)
 | 
				
			||||||
 | 
					          allow(foo).to receive(:hoge_file_name).and_return(nil)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          foo.hoge_remote_url = url
 | 
				
			||||||
 | 
					          expect(request).to have_been_requested
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
 | 
					      context "scheme is https, parsed_url.host isn't empty, and foo[attribute_name] != url" do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -34,11 +34,10 @@ RSpec.describe HomeFeed, type: :model do
 | 
				
			||||||
        Redis.current.set("account:#{account.id}:regeneration", true)
 | 
					        Redis.current.set("account:#{account.id}:regeneration", true)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'gets statuses with ids in the range from database' do
 | 
					      it 'returns nothing' do
 | 
				
			||||||
        results = subject.get(3)
 | 
					        results = subject.get(3)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(results.map(&:id)).to eq [10, 3, 2]
 | 
					        expect(results.map(&:id)).to eq []
 | 
				
			||||||
        expect(results.first.attributes.keys).to include('id', 'updated_at')
 | 
					 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -333,49 +333,6 @@ RSpec.describe Status, type: :model do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe '.as_home_timeline' do
 | 
					 | 
				
			||||||
    let(:account) { Fabricate(:account) }
 | 
					 | 
				
			||||||
    let(:followed) { Fabricate(:account) }
 | 
					 | 
				
			||||||
    let(:not_followed) { Fabricate(:account) }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    before do
 | 
					 | 
				
			||||||
      Fabricate(:follow, account: account, target_account: followed)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @self_status = Fabricate(:status, account: account, visibility: :public)
 | 
					 | 
				
			||||||
      @self_direct_status = Fabricate(:status, account: account, visibility: :direct)
 | 
					 | 
				
			||||||
      @followed_status = Fabricate(:status, account: followed, visibility: :public)
 | 
					 | 
				
			||||||
      @followed_direct_status = Fabricate(:status, account: followed, visibility: :direct)
 | 
					 | 
				
			||||||
      @not_followed_status = Fabricate(:status, account: not_followed, visibility: :public)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      @results = Status.as_home_timeline(account)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'includes statuses from self' do
 | 
					 | 
				
			||||||
      expect(@results).to include(@self_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'does not include direct statuses from self' do
 | 
					 | 
				
			||||||
      expect(@results).to_not include(@self_direct_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'includes statuses from followed' do
 | 
					 | 
				
			||||||
      expect(@results).to include(@followed_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'does not include direct statuses mentioning recipient from followed' do
 | 
					 | 
				
			||||||
      Fabricate(:mention, account: account, status: @followed_direct_status)
 | 
					 | 
				
			||||||
      expect(@results).to_not include(@followed_direct_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'does not include direct statuses not mentioning recipient from followed' do
 | 
					 | 
				
			||||||
      expect(@results).not_to include(@followed_direct_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'does not include statuses from non-followed' do
 | 
					 | 
				
			||||||
      expect(@results).not_to include(@not_followed_status)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe '.as_direct_timeline' do
 | 
					  describe '.as_direct_timeline' do
 | 
				
			||||||
    let(:account) { Fabricate(:account) }
 | 
					    let(:account) { Fabricate(:account) }
 | 
				
			||||||
    let(:followed) { Fabricate(:account) }
 | 
					    let(:followed) { Fabricate(:account) }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										52
									
								
								yarn.lock
								
								
								
								
							
							
						
						
									
										52
									
								
								yarn.lock
								
								
								
								
							| 
						 | 
					@ -3921,10 +3921,10 @@ eslint@^2.7.0:
 | 
				
			||||||
    text-table "~0.2.0"
 | 
					    text-table "~0.2.0"
 | 
				
			||||||
    user-home "^2.0.0"
 | 
					    user-home "^2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
eslint@^6.4.0:
 | 
					eslint@^6.5.0:
 | 
				
			||||||
  version "6.4.0"
 | 
					  version "6.5.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.4.0.tgz#5aa9227c3fbe921982b2eda94ba0d7fae858611a"
 | 
					  resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.0.tgz#304623eec903969dd5c9f2d61c6ce3d6ecec8750"
 | 
				
			||||||
  integrity sha512-WTVEzK3lSFoXUovDHEbkJqCVPEPwbhCq4trDktNI6ygs7aO41d4cDT0JFAT5MivzZeVLWlg7vHL+bgrQv/t3vA==
 | 
					  integrity sha512-IIbSW+vKOqMatPmS9ayyku4tvWxHY2iricSRtOz6+ZA5IPRlgXzEL0u/j6dr4eha0ugmhMwDTqxtmNu3kj9O4w==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/code-frame" "^7.0.0"
 | 
					    "@babel/code-frame" "^7.0.0"
 | 
				
			||||||
    ajv "^6.10.0"
 | 
					    ajv "^6.10.0"
 | 
				
			||||||
| 
						 | 
					@ -4005,12 +4005,7 @@ esrecurse@^4.1.0:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    estraverse "^4.1.0"
 | 
					    estraverse "^4.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
estraverse@^4.0.0, estraverse@^4.2.0:
 | 
					estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
 | 
				
			||||||
  version "4.2.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
 | 
					 | 
				
			||||||
  integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
estraverse@^4.1.0, estraverse@^4.1.1:
 | 
					 | 
				
			||||||
  version "4.3.0"
 | 
					  version "4.3.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
 | 
					  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
 | 
				
			||||||
  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 | 
					  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
 | 
				
			||||||
| 
						 | 
					@ -7009,11 +7004,6 @@ node-fetch@^1.0.1:
 | 
				
			||||||
    encoding "^0.1.11"
 | 
					    encoding "^0.1.11"
 | 
				
			||||||
    is-stream "^1.0.1"
 | 
					    is-stream "^1.0.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
node-fetch@^2.3.0:
 | 
					 | 
				
			||||||
  version "2.6.0"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
 | 
					 | 
				
			||||||
  integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
node-forge@0.8.2:
 | 
					node-forge@0.8.2:
 | 
				
			||||||
  version "0.8.2"
 | 
					  version "0.8.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.2.tgz#b4bcc59fb12ce77a8825fc6a783dfe3182499c5a"
 | 
					  resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.8.2.tgz#b4bcc59fb12ce77a8825fc6a783dfe3182499c5a"
 | 
				
			||||||
| 
						 | 
					@ -9398,21 +9388,16 @@ selfsigned@^1.10.6:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    node-forge "0.8.2"
 | 
					    node-forge "0.8.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.1, semver@^5.7.0:
 | 
					"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0:
 | 
				
			||||||
  version "5.7.0"
 | 
					  version "5.7.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
 | 
					  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
 | 
				
			||||||
  integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
 | 
					  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
semver@4.3.2:
 | 
					semver@4.3.2:
 | 
				
			||||||
  version "4.3.2"
 | 
					  version "4.3.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
 | 
					  resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.2.tgz#c7a07158a80bedd052355b770d82d6640f803be7"
 | 
				
			||||||
  integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
 | 
					  integrity sha1-x6BxWKgL7dBSNVt3DYLWZA+AO+c=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
semver@^5.3.0, semver@^5.5.0, semver@^5.6.0:
 | 
					 | 
				
			||||||
  version "5.7.1"
 | 
					 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
 | 
					 | 
				
			||||||
  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
 | 
					semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.2.0, semver@^6.3.0:
 | 
				
			||||||
  version "6.3.0"
 | 
					  version "6.3.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
 | 
					  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
 | 
				
			||||||
| 
						 | 
					@ -10103,10 +10088,10 @@ terser@^4.1.2:
 | 
				
			||||||
    source-map "~0.6.1"
 | 
					    source-map "~0.6.1"
 | 
				
			||||||
    source-map-support "~0.5.12"
 | 
					    source-map-support "~0.5.12"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tesseract.js-core@^2.0.0-beta.11:
 | 
					tesseract.js-core@^2.0.0-beta.12:
 | 
				
			||||||
  version "2.0.0-beta.11"
 | 
					  version "2.0.0-beta.13"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.0.0-beta.11.tgz#c35e3e689efad30138603977ad7eaaac44c7fd37"
 | 
					  resolved "https://registry.yarnpkg.com/tesseract.js-core/-/tesseract.js-core-2.0.0-beta.13.tgz#a21d798e88098898a9bdd935d0553215e03274f8"
 | 
				
			||||||
  integrity sha512-07haKH2JYYo0OfIJoioMS9dDiI5Hrl7+r1MqjeNAAT5WpKO0ATe4cpncC8s1kz0e3s1kaC5WOwL3YJcjbJE+hg==
 | 
					  integrity sha512-GboWV/aV5h+Whito6L6Q3WCFZ2+lgxZGgjY84wSpWbTLEkkZgHsU+dz1or+3rWSABH/nuzHDco1bZRk5+f94mw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tesseract.js-utils@^1.0.0-beta.8:
 | 
					tesseract.js-utils@^1.0.0-beta.8:
 | 
				
			||||||
  version "1.0.0-beta.8"
 | 
					  version "1.0.0-beta.8"
 | 
				
			||||||
| 
						 | 
					@ -10120,18 +10105,17 @@ tesseract.js-utils@^1.0.0-beta.8:
 | 
				
			||||||
    is-url "^1.2.4"
 | 
					    is-url "^1.2.4"
 | 
				
			||||||
    zlibjs "^0.3.1"
 | 
					    zlibjs "^0.3.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
tesseract.js@^2.0.0-alpha.15:
 | 
					tesseract.js@^2.0.0-alpha.16:
 | 
				
			||||||
  version "2.0.0-alpha.15"
 | 
					  version "2.0.0-alpha.16"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.0.0-alpha.15.tgz#9887f4d1c10e25bb098fde7a10580c865c362fad"
 | 
					  resolved "https://registry.yarnpkg.com/tesseract.js/-/tesseract.js-2.0.0-alpha.16.tgz#1e17717234a1464481abe12283f2c3ac79603d2e"
 | 
				
			||||||
  integrity sha512-qM1XUFVlTO+tx6oVRpd9QQ8PwQLxo3qhbfIHByUlUVIqWx6y/U9xlHIaG033/Tjfs2EQ0NAehPTOJ+eNElsXEg==
 | 
					  integrity sha512-8g3je2Kl8rkAFtpmwilGGj+8rCiPClNQaCjW6IafOPNn7hzFnVdL6fU6rG1Xsrc4Twv0HOa75kbpx5u70/WbTA==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    axios "^0.18.0"
 | 
					    axios "^0.18.0"
 | 
				
			||||||
    check-types "^7.4.0"
 | 
					    check-types "^7.4.0"
 | 
				
			||||||
    is-url "1.2.2"
 | 
					    is-url "1.2.2"
 | 
				
			||||||
    node-fetch "^2.3.0"
 | 
					 | 
				
			||||||
    opencollective-postinstall "^2.0.2"
 | 
					    opencollective-postinstall "^2.0.2"
 | 
				
			||||||
    resolve-url "^0.2.1"
 | 
					    resolve-url "^0.2.1"
 | 
				
			||||||
    tesseract.js-core "^2.0.0-beta.11"
 | 
					    tesseract.js-core "^2.0.0-beta.12"
 | 
				
			||||||
    tesseract.js-utils "^1.0.0-beta.8"
 | 
					    tesseract.js-utils "^1.0.0-beta.8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test-exclude@^5.0.0:
 | 
					test-exclude@^5.0.0:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue