forked from treehouse/mastodon
Merge pull request #1297 from ThibG/glitch-soc/merge-upstream
Merge upstream changesrebase/4.0.0rc2
commit
840b17e344
|
@ -6,6 +6,7 @@ aliases:
|
||||||
- image: circleci/ruby:2.7-buster-node
|
- image: circleci/ruby:2.7-buster-node
|
||||||
environment: &ruby_environment
|
environment: &ruby_environment
|
||||||
BUNDLE_APP_CONFIG: ./.bundle/
|
BUNDLE_APP_CONFIG: ./.bundle/
|
||||||
|
BUNDLE_PATH: ./vendor/bundle/
|
||||||
DB_HOST: localhost
|
DB_HOST: localhost
|
||||||
DB_USER: root
|
DB_USER: root
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
|
|
4
Gemfile
4
Gemfile
|
@ -49,7 +49,7 @@ gem 'omniauth-saml', '~> 1.10'
|
||||||
gem 'omniauth', '~> 1.9'
|
gem 'omniauth', '~> 1.9'
|
||||||
|
|
||||||
gem 'discard', '~> 1.1'
|
gem 'discard', '~> 1.1'
|
||||||
gem 'doorkeeper', '~> 5.2'
|
gem 'doorkeeper', '~> 5.3'
|
||||||
gem 'fast_blank', '~> 1.0'
|
gem 'fast_blank', '~> 1.0'
|
||||||
gem 'fastimage'
|
gem 'fastimage'
|
||||||
gem 'goldfinger', '~> 2.1'
|
gem 'goldfinger', '~> 2.1'
|
||||||
|
@ -92,7 +92,7 @@ gem 'simple-navigation', '~> 4.1'
|
||||||
gem 'simple_form', '~> 5.0'
|
gem 'simple_form', '~> 5.0'
|
||||||
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
gem 'sprockets-rails', '~> 3.2', require: 'sprockets/railtie'
|
||||||
gem 'stoplight', '~> 2.2.0'
|
gem 'stoplight', '~> 2.2.0'
|
||||||
gem 'strong_migrations', '~> 0.5'
|
gem 'strong_migrations', '~> 0.6'
|
||||||
gem 'tty-command', '~> 0.9', require: false
|
gem 'tty-command', '~> 0.9', require: false
|
||||||
gem 'tty-prompt', '~> 0.20', require: false
|
gem 'tty-prompt', '~> 0.20', require: false
|
||||||
gem 'twitter-text', '~> 1.14'
|
gem 'twitter-text', '~> 1.14'
|
||||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -195,7 +195,7 @@ GEM
|
||||||
docile (1.3.2)
|
docile (1.3.2)
|
||||||
domain_name (0.5.20190701)
|
domain_name (0.5.20190701)
|
||||||
unf (>= 0.0.5, < 1.0.0)
|
unf (>= 0.0.5, < 1.0.0)
|
||||||
doorkeeper (5.2.3)
|
doorkeeper (5.3.1)
|
||||||
railties (>= 5)
|
railties (>= 5)
|
||||||
dotenv (2.7.5)
|
dotenv (2.7.5)
|
||||||
dotenv-rails (2.7.5)
|
dotenv-rails (2.7.5)
|
||||||
|
@ -311,7 +311,7 @@ GEM
|
||||||
multi_json (~> 1.14)
|
multi_json (~> 1.14)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
json-ld-preloaded (3.1.0)
|
json-ld-preloaded (3.1.1)
|
||||||
json-ld (~> 3.1)
|
json-ld (~> 3.1)
|
||||||
rdf (~> 3.1)
|
rdf (~> 3.1)
|
||||||
jsonapi-renderer (0.2.2)
|
jsonapi-renderer (0.2.2)
|
||||||
|
@ -383,7 +383,7 @@ GEM
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sidekiq (>= 3.5)
|
sidekiq (>= 3.5)
|
||||||
statsd-ruby (~> 1.4, >= 1.4.0)
|
statsd-ruby (~> 1.4, >= 1.4.0)
|
||||||
oj (3.10.1)
|
oj (3.10.3)
|
||||||
omniauth (1.9.0)
|
omniauth (1.9.0)
|
||||||
hashie (>= 3.4.6, < 3.7.0)
|
hashie (>= 3.4.6, < 3.7.0)
|
||||||
rack (>= 1.6.2, < 3)
|
rack (>= 1.6.2, < 3)
|
||||||
|
@ -603,7 +603,7 @@ GEM
|
||||||
stoplight (2.2.0)
|
stoplight (2.2.0)
|
||||||
streamio-ffmpeg (3.0.2)
|
streamio-ffmpeg (3.0.2)
|
||||||
multi_json (~> 1.8)
|
multi_json (~> 1.8)
|
||||||
strong_migrations (0.5.1)
|
strong_migrations (0.6.2)
|
||||||
activerecord (>= 5)
|
activerecord (>= 5)
|
||||||
temple (0.8.2)
|
temple (0.8.2)
|
||||||
terminal-table (1.8.0)
|
terminal-table (1.8.0)
|
||||||
|
@ -690,7 +690,7 @@ DEPENDENCIES
|
||||||
devise-two-factor (~> 3.1)
|
devise-two-factor (~> 3.1)
|
||||||
devise_pam_authenticatable2 (~> 9.2)
|
devise_pam_authenticatable2 (~> 9.2)
|
||||||
discard (~> 1.1)
|
discard (~> 1.1)
|
||||||
doorkeeper (~> 5.2)
|
doorkeeper (~> 5.3)
|
||||||
dotenv-rails (~> 2.7)
|
dotenv-rails (~> 2.7)
|
||||||
e2mmap (~> 0.1.0)
|
e2mmap (~> 0.1.0)
|
||||||
fabrication (~> 2.21)
|
fabrication (~> 2.21)
|
||||||
|
@ -779,7 +779,7 @@ DEPENDENCIES
|
||||||
stackprof
|
stackprof
|
||||||
stoplight (~> 2.2.0)
|
stoplight (~> 2.2.0)
|
||||||
streamio-ffmpeg (~> 3.0)
|
streamio-ffmpeg (~> 3.0)
|
||||||
strong_migrations (~> 0.5)
|
strong_migrations (~> 0.6)
|
||||||
thor (~> 0.20)
|
thor (~> 0.20)
|
||||||
thwait (~> 0.1.0)
|
thwait (~> 0.1.0)
|
||||||
tty-command (~> 0.9)
|
tty-command (~> 0.9)
|
||||||
|
|
|
@ -6,7 +6,7 @@ class AccountFollowController < ApplicationController
|
||||||
before_action :authenticate_user!
|
before_action :authenticate_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
FollowService.new.call(current_user.account, @account.acct)
|
FollowService.new.call(current_user.account, @account, with_rate_limit: true)
|
||||||
redirect_to account_path(@account)
|
redirect_to account_path(@account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin
|
||||||
|
class SiteUploadsController < BaseController
|
||||||
|
before_action :set_site_upload
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
authorize :settings, :destroy?
|
||||||
|
|
||||||
|
@site_upload.destroy!
|
||||||
|
|
||||||
|
redirect_to edit_admin_settings_path, notice: I18n.t('admin.site_uploads.destroyed_msg')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_site_upload
|
||||||
|
@site_upload = SiteUpload.find(params[:id])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -44,6 +44,10 @@ class Api::BaseController < ApplicationController
|
||||||
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rescue_from Mastodon::RateLimitExceededError do
|
||||||
|
render json: { error: I18n.t('errors.429') }, status: 429
|
||||||
|
end
|
||||||
|
|
||||||
rescue_from ActionController::ParameterMissing do |e|
|
rescue_from ActionController::ParameterMissing do |e|
|
||||||
render json: { error: e.to_s }, status: 400
|
render json: { error: e.to_s }, status: 400
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowerAccountsController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::Accounts::FollowingAccountsController < Api::BaseController
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::Accounts::IdentityProofsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@proofs = @account.identity_proofs.active
|
@proofs = @account.identity_proofs.active
|
||||||
render json: @proofs, each_serializer: REST::IdentityProofSerializer
|
render json: @proofs, each_serializer: REST::IdentityProofSerializer
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::Accounts::ListsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@lists = @account.lists.where(account: current_account)
|
@lists = @account.lists.where(account: current_account)
|
||||||
render json: @lists, each_serializer: REST::ListSerializer
|
render json: @lists, each_serializer: REST::ListSerializer
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Accounts::PinsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
AccountPin.create!(account: current_account, target_account: @account)
|
AccountPin.create!(account: current_account, target_account: @account)
|
||||||
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships_presenter
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::Accounts::RelationshipsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
before_action -> { doorkeeper_authorize! :read, :'read:follows' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
accounts = Account.where(id: account_ids).select('id')
|
accounts = Account.where(id: account_ids).select('id')
|
||||||
# .where doesn't guarantee that our results are in the same order
|
# .where doesn't guarantee that our results are in the same order
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::Accounts::SearchController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@accounts = account_search
|
@accounts = account_search
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -6,8 +6,6 @@ class Api::V1::Accounts::StatusesController < Api::BaseController
|
||||||
|
|
||||||
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
after_action :insert_pagination_headers, unless: -> { truthy_param?(:pinned) }
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
|
|
@ -14,7 +14,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
|
|
||||||
skip_before_action :require_authenticated_user!, only: :create
|
skip_before_action :require_authenticated_user!, only: :create
|
||||||
|
|
||||||
respond_to :json
|
override_rate_limit_headers :follow, family: :follows
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: @account, serializer: REST::AccountSerializer
|
render json: @account, serializer: REST::AccountSerializer
|
||||||
|
@ -31,7 +31,7 @@ class Api::V1::AccountsController < Api::BaseController
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow
|
def follow
|
||||||
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs))
|
FollowService.new.call(current_user.account, @account, reblogs: truthy_param?(:reblogs), with_rate_limit: true)
|
||||||
|
|
||||||
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
|
options = @account.locked? || current_user.account.silenced? ? {} : { following_map: { @account.id => { reblogs: truthy_param?(:reblogs) } }, requested_map: { @account.id => false } }
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
class Api::V1::Apps::CredentialsController < Api::BaseController
|
class Api::V1::Apps::CredentialsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read }
|
before_action -> { doorkeeper_authorize! :read }
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
|
render json: doorkeeper_token.application, serializer: REST::ApplicationSerializer, fields: %i(name website vapid_key)
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::BlocksController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::BookmarksController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
|
|
@ -9,8 +9,6 @@ class Api::V1::ConversationsController < Api::BaseController
|
||||||
before_action :set_conversation, except: :index
|
before_action :set_conversation, except: :index
|
||||||
after_action :insert_pagination_headers, only: :index
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@conversations = paginated_conversations
|
@conversations = paginated_conversations
|
||||||
render json: @conversations, each_serializer: REST::ConversationSerializer
|
render json: @conversations, each_serializer: REST::ConversationSerializer
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::CustomEmojisController < Api::BaseController
|
class Api::V1::CustomEmojisController < Api::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
skip_before_action :set_cache_headers
|
skip_before_action :set_cache_headers
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
|
|
@ -8,8 +8,6 @@ class Api::V1::DomainBlocksController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers, only: :show
|
after_action :insert_pagination_headers, only: :show
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@blocks = load_domain_blocks
|
@blocks = load_domain_blocks
|
||||||
render json: @blocks.map(&:domain)
|
render json: @blocks.map(&:domain)
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::EndorsementsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::FavouritesController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
|
|
@ -2,12 +2,9 @@
|
||||||
|
|
||||||
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
class Api::V1::FeaturedTags::SuggestionsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
|
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }, only: :index
|
||||||
|
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_most_used_tags, only: :index
|
before_action :set_most_used_tags, only: :index
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @most_used_tags, each_serializer: REST::TagSerializer
|
render json: @most_used_tags, each_serializer: REST::TagSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::FiltersController < Api::BaseController
|
||||||
before_action :set_filters, only: :index
|
before_action :set_filters, only: :index
|
||||||
before_action :set_filter, only: [:show, :update, :destroy]
|
before_action :set_filter, only: [:show, :update, :destroy]
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @filters, each_serializer: REST::FilterSerializer
|
render json: @filters, each_serializer: REST::FilterSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,8 +6,6 @@ class Api::V1::Instances::ActivityController < Api::BaseController
|
||||||
skip_before_action :set_cache_headers
|
skip_before_action :set_cache_headers
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
expires_in 1.day, public: true
|
expires_in 1.day, public: true
|
||||||
render_with_cache json: :activity, expires_in: 1.day
|
render_with_cache json: :activity, expires_in: 1.day
|
||||||
|
|
|
@ -6,8 +6,6 @@ class Api::V1::Instances::PeersController < Api::BaseController
|
||||||
skip_before_action :set_cache_headers
|
skip_before_action :set_cache_headers
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
expires_in 1.day, public: true
|
expires_in 1.day, public: true
|
||||||
render_with_cache(expires_in: 1.day) { Account.remote.domains }
|
render_with_cache(expires_in: 1.day) { Account.remote.domains }
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::InstancesController < Api::BaseController
|
class Api::V1::InstancesController < Api::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
skip_before_action :set_cache_headers
|
skip_before_action :set_cache_headers
|
||||||
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::MediaController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
before_action -> { doorkeeper_authorize! :write, :'write:media' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@media = current_account.media_attachments.create!(media_params)
|
@media = current_account.media_attachments.create!(media_params)
|
||||||
render json: @media, serializer: REST::MediaAttachmentSerializer
|
render json: @media, serializer: REST::MediaAttachmentSerializer
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::MutesController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@data = @accounts = load_accounts
|
@data = @accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -6,8 +6,6 @@ class Api::V1::NotificationsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
after_action :insert_pagination_headers, only: :index
|
after_action :insert_pagination_headers, only: :index
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
DEFAULT_NOTIFICATIONS_LIMIT = 15
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Polls::VotesController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_poll
|
before_action :set_poll
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
VoteService.new.call(current_account, @poll, vote_params[:choices])
|
VoteService.new.call(current_account, @poll, vote_params[:choices])
|
||||||
render json: @poll, serializer: REST::PollSerializer
|
render json: @poll, serializer: REST::PollSerializer
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::PollsController < Api::BaseController
|
||||||
before_action :set_poll
|
before_action :set_poll
|
||||||
before_action :refresh_poll
|
before_action :refresh_poll
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: @poll, serializer: REST::PollSerializer, include_results: true
|
render json: @poll, serializer: REST::PollSerializer, include_results: true
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::PreferencesController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
before_action -> { doorkeeper_authorize! :read, :'read:accounts' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: current_account, serializer: REST::PreferencesSerializer
|
render json: current_account, serializer: REST::PreferencesSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::ReportsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
|
before_action -> { doorkeeper_authorize! :write, :'write:reports' }, only: [:create]
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@report = ReportService.new.call(
|
@report = ReportService.new.call(
|
||||||
current_account,
|
current_account,
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
|
current_account.bookmarks.find_or_create_by!(account: current_account, status: @status)
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
FavouriteService.new.call(current_account, @status)
|
FavouriteService.new.call(current_account, @status)
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
|
|
|
@ -8,8 +8,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
before_action :set_conversation
|
before_action :set_conversation
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
current_account.mute_conversation!(@conversation)
|
current_account.mute_conversation!(@conversation)
|
||||||
@mutes_map = { @conversation.id => true }
|
@mutes_map = { @conversation.id => true }
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def create
|
def create
|
||||||
StatusPin.create!(account: current_account, status: @status)
|
StatusPin.create!(account: current_account, status: @status)
|
||||||
distribute_add_activity!
|
distribute_add_activity!
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
|
||||||
before_action :set_status
|
before_action :set_status
|
||||||
after_action :insert_pagination_headers
|
after_action :insert_pagination_headers
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@accounts = load_accounts
|
@accounts = load_accounts
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
|
|
|
@ -7,10 +7,11 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_reblog
|
before_action :set_reblog
|
||||||
|
|
||||||
respond_to :json
|
override_rate_limit_headers :create, family: :statuses
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
|
@status = ReblogService.new.call(current_account, @reblog, reblog_params)
|
||||||
|
|
||||||
render json: @status, serializer: REST::StatusSerializer
|
render json: @status, serializer: REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
before_action :require_user!, except: [:show, :context]
|
before_action :require_user!, except: [:show, :context]
|
||||||
before_action :set_status, only: [:show, :context]
|
before_action :set_status, only: [:show, :context]
|
||||||
|
|
||||||
respond_to :json
|
override_rate_limit_headers :create, family: :statuses
|
||||||
|
|
||||||
# This API was originally unlimited, pagination cannot be introduced without
|
# This API was originally unlimited, pagination cannot be introduced without
|
||||||
# breaking backwards-compatibility. Arbitrarily high number to cover most
|
# breaking backwards-compatibility. Arbitrarily high number to cover most
|
||||||
|
@ -45,7 +45,8 @@ class Api::V1::StatusesController < Api::BaseController
|
||||||
application: doorkeeper_token.application,
|
application: doorkeeper_token.application,
|
||||||
poll: status_params[:poll],
|
poll: status_params[:poll],
|
||||||
content_type: status_params[:content_type],
|
content_type: status_params[:content_type],
|
||||||
idempotency: request.headers['Idempotency-Key'])
|
idempotency: request.headers['Idempotency-Key'],
|
||||||
|
with_rate_limit: true)
|
||||||
|
|
||||||
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
render json: @status, serializer: @status.is_a?(ScheduledStatus) ? REST::ScheduledStatusSerializer : REST::StatusSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::StreamingController < Api::BaseController
|
class Api::V1::StreamingController < Api::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
if Rails.configuration.x.streaming_api_base_url != request.host
|
if Rails.configuration.x.streaming_api_base_url != request.host
|
||||||
redirect_to streaming_api_url, status: 301
|
redirect_to streaming_api_url, status: 301
|
||||||
|
|
|
@ -7,8 +7,6 @@ class Api::V1::SuggestionsController < Api::BaseController
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
before_action :set_accounts
|
before_action :set_accounts
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,8 +5,6 @@ class Api::V1::Timelines::HomeController < Api::BaseController
|
||||||
before_action :require_user!, only: [:show]
|
before_action :require_user!, only: [:show]
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::Timelines::PublicController < Api::BaseController
|
||||||
before_action :require_user!, only: [:show], if: :require_auth?
|
before_action :require_user!, only: [:show], if: :require_auth?
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
|
|
@ -4,8 +4,6 @@ class Api::V1::Timelines::TagController < Api::BaseController
|
||||||
before_action :load_tag
|
before_action :load_tag
|
||||||
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
after_action :insert_pagination_headers, unless: -> { @statuses.empty? }
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def show
|
def show
|
||||||
@statuses = load_statuses
|
@statuses = load_statuses
|
||||||
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id)
|
||||||
|
|
|
@ -3,8 +3,6 @@
|
||||||
class Api::V1::TrendsController < Api::BaseController
|
class Api::V1::TrendsController < Api::BaseController
|
||||||
before_action :set_tags
|
before_action :set_tags
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
render json: @tags, each_serializer: REST::TagSerializer
|
render json: @tags, each_serializer: REST::TagSerializer
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,8 +8,6 @@ class Api::V2::SearchController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
before_action -> { doorkeeper_authorize! :read, :'read:search' }
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@search = Search.new(search_results)
|
@search = Search.new(search_results)
|
||||||
render json: @search, serializer: REST::SearchSerializer
|
render json: @search, serializer: REST::SearchSerializer
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Web::EmbedsController < Api::Web::BaseController
|
class Api::Web::EmbedsController < Api::Web::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
class Api::Web::PushSubscriptionsController < Api::Web::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::Web::SettingsController < Api::Web::BaseController
|
class Api::Web::SettingsController < Api::Web::BaseController
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
before_action :require_user!
|
before_action :require_user!
|
||||||
|
|
||||||
def update
|
def update
|
||||||
|
|
|
@ -30,6 +30,7 @@ class ApplicationController < ActionController::Base
|
||||||
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
rescue_from Mastodon::NotPermittedError, with: :forbidden
|
||||||
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
|
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
|
||||||
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
|
rescue_from Mastodon::RaceConditionError, with: :service_unavailable
|
||||||
|
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
|
||||||
|
|
||||||
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
|
||||||
before_action :require_functional!, if: :user_signed_in?
|
before_action :require_functional!, if: :user_signed_in?
|
||||||
|
@ -181,6 +182,10 @@ class ApplicationController < ActionController::Base
|
||||||
respond_with_error(503)
|
respond_with_error(503)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def too_many_requests
|
||||||
|
respond_with_error(429)
|
||||||
|
end
|
||||||
|
|
||||||
def single_user_mode?
|
def single_user_mode?
|
||||||
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
|
@single_user_mode ||= Rails.configuration.x.single_user_mode && Account.where('id > 0').exists?
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ class AuthorizeInteractionsController < ApplicationController
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource)
|
if @resource.is_a?(Account) && FollowService.new.call(current_account, @resource, with_rate_limit: true)
|
||||||
render :success
|
render :success
|
||||||
else
|
else
|
||||||
render :error
|
render :error
|
||||||
|
|
|
@ -3,6 +3,20 @@
|
||||||
module RateLimitHeaders
|
module RateLimitHeaders
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
class_methods do
|
||||||
|
def override_rate_limit_headers(method_name, options = {})
|
||||||
|
around_action(only: method_name, if: :current_account) do |_controller, block|
|
||||||
|
begin
|
||||||
|
block.call
|
||||||
|
ensure
|
||||||
|
rate_limiter = RateLimiter.new(current_account, options)
|
||||||
|
rate_limit_headers = rate_limiter.to_headers
|
||||||
|
response.headers.merge!(rate_limit_headers) unless response.headers['X-RateLimit-Remaining'].present? && rate_limit_headers['X-RateLimit-Remaining'].to_i > response.headers['X-RateLimit-Remaining'].to_i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
included do
|
included do
|
||||||
before_action :set_rate_limit_headers, if: :rate_limited_request?
|
before_action :set_rate_limit_headers, if: :rate_limited_request?
|
||||||
end
|
end
|
||||||
|
@ -44,7 +58,7 @@ module RateLimitHeaders
|
||||||
end
|
end
|
||||||
|
|
||||||
def api_throttle_data
|
def api_throttle_data
|
||||||
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] }
|
most_limited_type, = request.env['rack.attack.throttle_data'].min_by { |_, v| v[:limit] - v[:count] }
|
||||||
request.env['rack.attack.throttle_data'][most_limited_type]
|
request.env['rack.attack.throttle_data'][most_limited_type]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
module Admin::SettingsHelper
|
||||||
|
def site_upload_delete_hint(hint, var)
|
||||||
|
upload = SiteUpload.find_by(var: var.to_s)
|
||||||
|
return hint unless upload
|
||||||
|
|
||||||
|
link = link_to t('admin.site_uploads.delete'), admin_site_upload_path(upload), data: { method: :delete }
|
||||||
|
safe_join([hint, link], '<br/>'.html_safe)
|
||||||
|
end
|
||||||
|
end
|
|
@ -45,7 +45,7 @@ export default class IntersectionObserverArticle extends React.Component {
|
||||||
intersectionObserverWrapper.observe(
|
intersectionObserverWrapper.observe(
|
||||||
id,
|
id,
|
||||||
this.node,
|
this.node,
|
||||||
this.handleIntersection
|
this.handleIntersection,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.componentMounted = true;
|
this.componentMounted = true;
|
||||||
|
|
|
@ -23,7 +23,7 @@ const messages = defineMessages({
|
||||||
id: 'status.sensitive_toggle',
|
id: 'status.sensitive_toggle',
|
||||||
},
|
},
|
||||||
toggle_visible: {
|
toggle_visible: {
|
||||||
defaultMessage: 'Toggle visibility',
|
defaultMessage: 'Hide media',
|
||||||
id: 'media_gallery.toggle_visible',
|
id: 'media_gallery.toggle_visible',
|
||||||
},
|
},
|
||||||
warning: {
|
warning: {
|
||||||
|
|
|
@ -30,8 +30,8 @@ const messages = defineMessages({
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||||
|
|
|
@ -199,8 +199,8 @@ class Audio extends React.PureComponent {
|
||||||
<div className='video-player__controls active'>
|
<div className='video-player__controls active'>
|
||||||
<div className='video-player__buttons-bar'>
|
<div className='video-player__buttons-bar'>
|
||||||
<div className='video-player__buttons left'>
|
<div className='video-player__buttons left'>
|
||||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
||||||
|
@ -221,7 +221,7 @@ class Audio extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
<button type='button' aria-label={intl.formatMessage(messages.download)}>
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}>
|
||||||
<a className='video-player__download__icon' href={this.props.src} download>
|
<a className='video-player__download__icon' href={this.props.src} download>
|
||||||
<Icon id={'download'} fixedWidth />
|
<Icon id={'download'} fixedWidth />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />
|
<AccountContainer key={id} id={id} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -34,7 +34,7 @@ const messages = defineMessages({
|
||||||
id: 'content-type.change',
|
id: 'content-type.change',
|
||||||
},
|
},
|
||||||
direct_long: {
|
direct_long: {
|
||||||
defaultMessage: 'Post to mentioned users only',
|
defaultMessage: 'Visible for mentioned users only',
|
||||||
id: 'privacy.direct.long',
|
id: 'privacy.direct.long',
|
||||||
},
|
},
|
||||||
direct_short: {
|
direct_short: {
|
||||||
|
@ -66,7 +66,7 @@ const messages = defineMessages({
|
||||||
id: 'compose.content-type.plain',
|
id: 'compose.content-type.plain',
|
||||||
},
|
},
|
||||||
private_long: {
|
private_long: {
|
||||||
defaultMessage: 'Post to followers only',
|
defaultMessage: 'Visible for followers only',
|
||||||
id: 'privacy.private.long',
|
id: 'privacy.private.long',
|
||||||
},
|
},
|
||||||
private_short: {
|
private_short: {
|
||||||
|
@ -74,7 +74,7 @@ const messages = defineMessages({
|
||||||
id: 'privacy.private.short',
|
id: 'privacy.private.short',
|
||||||
},
|
},
|
||||||
public_long: {
|
public_long: {
|
||||||
defaultMessage: 'Post to public timelines',
|
defaultMessage: 'Visible for all, shown in public timelines',
|
||||||
id: 'privacy.public.long',
|
id: 'privacy.public.long',
|
||||||
},
|
},
|
||||||
public_short: {
|
public_short: {
|
||||||
|
@ -94,7 +94,7 @@ const messages = defineMessages({
|
||||||
id: 'advanced_options.threaded_mode.short',
|
id: 'advanced_options.threaded_mode.short',
|
||||||
},
|
},
|
||||||
unlisted_long: {
|
unlisted_long: {
|
||||||
defaultMessage: 'Do not show in public timelines',
|
defaultMessage: 'Visible for all, but not in public timelines',
|
||||||
id: 'privacy.unlisted.long',
|
id: 'privacy.unlisted.long',
|
||||||
},
|
},
|
||||||
unlisted_short: {
|
unlisted_short: {
|
||||||
|
|
|
@ -143,6 +143,7 @@ class PollForm extends ImmutablePureComponent {
|
||||||
<option value='true'>{intl.formatMessage(messages.multiple_choices)}</option>
|
<option value='true'>{intl.formatMessage(messages.multiple_choices)}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/no-onchange */}
|
||||||
<select value={expiresIn} onChange={this.handleSelectDuration}>
|
<select value={expiresIn} onChange={this.handleSelectDuration}>
|
||||||
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
||||||
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
||||||
|
|
|
@ -67,7 +67,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{domains.map(domain =>
|
{domains.map(domain =>
|
||||||
<DomainContainer key={domain} domain={domain} />
|
<DomainContainer key={domain} domain={domain} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -88,7 +88,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -67,7 +67,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountAuthorizeContainer key={id} id={id} />
|
<AccountAuthorizeContainer key={id} id={id} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -105,7 +105,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -105,7 +105,7 @@ class Following extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -95,6 +95,10 @@ class Content extends ImmutablePureComponent {
|
||||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||||
} else {
|
} else {
|
||||||
|
let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url'));
|
||||||
|
if (status) {
|
||||||
|
link.addEventListener('click', this.onStatusClick.bind(this, status), false);
|
||||||
|
}
|
||||||
link.setAttribute('title', link.href);
|
link.setAttribute('title', link.href);
|
||||||
link.classList.add('unhandled-link');
|
link.classList.add('unhandled-link');
|
||||||
}
|
}
|
||||||
|
@ -120,6 +124,13 @@ class Content extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStatusClick = (status, e) => {
|
||||||
|
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.router.history.push(`/statuses/${status.get('id')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
handleEmojiMouseEnter = ({ target }) => {
|
||||||
target.src = target.getAttribute('data-original');
|
target.src = target.getAttribute('data-original');
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,7 +73,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{lists.map(list =>
|
{lists.map(list =>
|
||||||
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -66,7 +66,7 @@ class Mutes extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />
|
<AccountContainer key={id} id={id} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -89,7 +89,7 @@ class Reblogs extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -90,7 +90,7 @@ export default class Card extends React.PureComponent {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
0
|
0,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -488,8 +488,9 @@ class Video extends React.PureComponent {
|
||||||
|
|
||||||
<div className='video-player__buttons-bar'>
|
<div className='video-player__buttons-bar'>
|
||||||
<div className='video-player__buttons left'>
|
<div className='video-player__buttons left'>
|
||||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay} autoFocus={detailed}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
||||||
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
<div className='video-player__volume__current' style={{ width: `${volumeWidth}px` }} />
|
||||||
|
@ -512,16 +513,11 @@ class Video extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
{(!onCloseVideo && !editable && !fullscreen) && <button type='button' aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
{(!onCloseVideo && !editable && !fullscreen) && <button type='button' title={intl.formatMessage(messages.hide)} aria-label={intl.formatMessage(messages.hide)} onClick={this.toggleReveal}><Icon id='eye-slash' fixedWidth /></button>}
|
||||||
{(!fullscreen && onOpenVideo) && <button type='button' aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
{(!fullscreen && onOpenVideo) && <button type='button' title={intl.formatMessage(messages.expand)} aria-label={intl.formatMessage(messages.expand)} onClick={this.handleOpenVideo}><Icon id='expand' fixedWidth /></button>}
|
||||||
{onCloseVideo && <button type='button' aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
{onCloseVideo && <button type='button' title={intl.formatMessage(messages.close)} aria-label={intl.formatMessage(messages.close)} onClick={this.handleCloseVideo}><Icon id='compress' fixedWidth /></button>}
|
||||||
<button type='button' aria-label={intl.formatMessage(messages.download)}>
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}><a className='video-player__download__icon' href={this.props.src} download><Icon id={'download'} fixedWidth /></a></button>
|
||||||
<a className='video-player__download__icon' href={this.props.src} download>
|
<button type='button' title={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
||||||
<Icon id={'download'} fixedWidth />
|
|
||||||
</a>
|
|
||||||
</button>
|
|
||||||
<button type='button' aria-label={intl.formatMessage(fullscreen ? messages.exit_fullscreen : messages.fullscreen)} onClick={this.toggleFullscreen}><Icon id={fullscreen ? 'compress' : 'arrows-alt'} fixedWidth /></button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -91,11 +91,11 @@ const expandNormalizedNotifications = (state, notifications, next, isLoadingRece
|
||||||
|
|
||||||
mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
|
mutable.update(usePendingItems ? 'pendingItems' : 'items', list => {
|
||||||
const lastIndex = 1 + list.findLastIndex(
|
const lastIndex = 1 + list.findLastIndex(
|
||||||
item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id'))
|
item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')),
|
||||||
);
|
);
|
||||||
|
|
||||||
const firstIndex = 1 + list.take(lastIndex).findLastIndex(
|
const firstIndex = 1 + list.take(lastIndex).findLastIndex(
|
||||||
item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0
|
item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
return list.take(firstIndex).concat(items, list.skip(lastIndex));
|
return list.take(firstIndex).concat(items, list.skip(lastIndex));
|
||||||
|
|
|
@ -53,7 +53,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is
|
||||||
|
|
||||||
return oldIds.take(firstIndex + 1).concat(
|
return oldIds.take(firstIndex + 1).concat(
|
||||||
isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds,
|
isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds,
|
||||||
oldIds.skip(lastIndex)
|
oldIds.skip(lastIndex),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -171,7 +171,7 @@ export default function timelines(state = initialState, action) {
|
||||||
return state.update(
|
return state.update(
|
||||||
action.timeline,
|
action.timeline,
|
||||||
initialTimeline,
|
initialTimeline,
|
||||||
map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items)
|
map => map.set('online', false).update(action.usePendingItems ? 'pendingItems' : 'items', items => items.first() ? items.unshift(null) : items),
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
|
|
|
@ -146,7 +146,7 @@ export const makeGetStatus = () => {
|
||||||
map.set('account', accountBase);
|
map.set('account', accountBase);
|
||||||
map.set('filtered', filtered);
|
map.set('filtered', filtered);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,6 @@ export default function configureStore() {
|
||||||
thunk,
|
thunk,
|
||||||
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
|
loadingBarMiddleware({ promiseTypeSuffixes: ['REQUEST', 'SUCCESS', 'FAIL'] }),
|
||||||
errorsMiddleware(),
|
errorsMiddleware(),
|
||||||
soundsMiddleware()
|
soundsMiddleware(),
|
||||||
), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
|
), window.__REDUX_DEVTOOLS_EXTENSION__ ? window.__REDUX_DEVTOOLS_EXTENSION__() : f => f));
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.announcements__item__content {
|
.announcements__item__content {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
.emojione {
|
.emojione {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
@ -69,17 +70,21 @@
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 15px;
|
padding: 15px;
|
||||||
padding-right: 15px + 18px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
&__range {
|
&__range {
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
padding-right: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__unread {
|
&__unread {
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
.emoji-picker-dropdown {
|
.emoji-picker-dropdown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 5px;
|
top: 0;
|
||||||
top: 5px;
|
right: 0;
|
||||||
|
|
||||||
::-webkit-scrollbar-track:hover,
|
::-webkit-scrollbar-track:hover,
|
||||||
::-webkit-scrollbar-track:active {
|
::-webkit-scrollbar-track:active {
|
||||||
|
|
|
@ -72,10 +72,7 @@
|
||||||
|
|
||||||
.emoji-button {
|
.emoji-button {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 24px;
|
padding: 5px 5px 2px 2px;
|
||||||
line-height: 24px;
|
|
||||||
margin-left: 2px;
|
|
||||||
width: 24px;
|
|
||||||
outline: 0;
|
outline: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
@ -91,7 +88,6 @@
|
||||||
margin: 0;
|
margin: 0;
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
margin-top: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
|
|
|
@ -62,12 +62,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.media-gallery__gifv {
|
.media-gallery__gifv {
|
||||||
&.autoplay {
|
|
||||||
.media-gallery__gifv__label {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.media-gallery__gifv__label {
|
.media-gallery__gifv__label {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
|
@ -106,7 +106,7 @@ export function fetchAccount(id) {
|
||||||
dispatch,
|
dispatch,
|
||||||
getState,
|
getState,
|
||||||
db.transaction('accounts', 'read').objectStore('accounts').index('id'),
|
db.transaction('accounts', 'read').objectStore('accounts').index('id'),
|
||||||
id
|
id,
|
||||||
).then(() => db.close(), error => {
|
).then(() => db.close(), error => {
|
||||||
db.close();
|
db.close();
|
||||||
throw error;
|
throw error;
|
||||||
|
|
|
@ -44,7 +44,7 @@ export default class IntersectionObserverArticle extends React.Component {
|
||||||
intersectionObserverWrapper.observe(
|
intersectionObserverWrapper.observe(
|
||||||
id,
|
id,
|
||||||
this.node,
|
this.node,
|
||||||
this.handleIntersection
|
this.handleIntersection,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.componentMounted = true;
|
this.componentMounted = true;
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { autoPlayGif, cropImages, displayMedia, useBlurhash } from '../initial_s
|
||||||
import { decode } from 'blurhash';
|
import { decode } from 'blurhash';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Toggle visibility' },
|
toggle_visible: { id: 'media_gallery.toggle_visible', defaultMessage: 'Hide media' },
|
||||||
});
|
});
|
||||||
|
|
||||||
class Item extends React.PureComponent {
|
class Item extends React.PureComponent {
|
||||||
|
|
|
@ -36,8 +36,8 @@ const messages = defineMessages({
|
||||||
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' },
|
||||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' },
|
||||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to status' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
|
||||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,8 +29,8 @@ const messages = defineMessages({
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
share: { id: 'account.share', defaultMessage: 'Share @{name}\'s profile' },
|
||||||
media: { id: 'account.media', defaultMessage: 'Media' },
|
media: { id: 'account.media', defaultMessage: 'Media' },
|
||||||
blockDomain: { id: 'account.block_domain', defaultMessage: 'Hide everything from {domain}' },
|
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
|
||||||
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unhide {domain}' },
|
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
|
||||||
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
hideReblogs: { id: 'account.hide_reblogs', defaultMessage: 'Hide boosts from @{name}' },
|
||||||
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
showReblogs: { id: 'account.show_reblogs', defaultMessage: 'Show boosts from @{name}' },
|
||||||
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
pins: { id: 'navigation_bar.pins', defaultMessage: 'Pinned toots' },
|
||||||
|
|
|
@ -214,8 +214,8 @@ class Audio extends React.PureComponent {
|
||||||
<div className='video-player__controls active'>
|
<div className='video-player__controls active'>
|
||||||
<div className='video-player__buttons-bar'>
|
<div className='video-player__buttons-bar'>
|
||||||
<div className='video-player__buttons left'>
|
<div className='video-player__buttons left'>
|
||||||
<button type='button' aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(paused ? messages.play : messages.pause)} aria-label={intl.formatMessage(paused ? messages.play : messages.pause)} onClick={this.togglePlay}><Icon id={paused ? 'play' : 'pause'} fixedWidth /></button>
|
||||||
<button type='button' aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
<button type='button' title={intl.formatMessage(muted ? messages.unmute : messages.mute)} aria-label={intl.formatMessage(muted ? messages.unmute : messages.mute)} onClick={this.toggleMute}><Icon id={muted ? 'volume-off' : 'volume-up'} fixedWidth /></button>
|
||||||
|
|
||||||
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
<div className='video-player__volume' onMouseDown={this.handleVolumeMouseDown} ref={this.setVolumeRef}>
|
||||||
|
|
||||||
|
@ -236,7 +236,7 @@ class Audio extends React.PureComponent {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='video-player__buttons right'>
|
<div className='video-player__buttons right'>
|
||||||
<button type='button' aria-label={intl.formatMessage(messages.download)}>
|
<button type='button' title={intl.formatMessage(messages.download)} aria-label={intl.formatMessage(messages.download)}>
|
||||||
<a className='video-player__download__icon' href={this.props.src} download>
|
<a className='video-player__download__icon' href={this.props.src} download>
|
||||||
<Icon id={'download'} fixedWidth />
|
<Icon id={'download'} fixedWidth />
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -68,7 +68,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} />
|
<AccountContainer key={id} id={id} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -155,6 +155,7 @@ class PollForm extends ImmutablePureComponent {
|
||||||
<div className='poll__footer'>
|
<div className='poll__footer'>
|
||||||
<button disabled={options.size >= 5} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
|
<button disabled={options.size >= 5} className='button button-secondary' onClick={this.handleAddOption}><Icon id='plus' /> <FormattedMessage {...messages.add_option} /></button>
|
||||||
|
|
||||||
|
{/* eslint-disable-next-line jsx-a11y/no-onchange */}
|
||||||
<select value={expiresIn} onChange={this.handleSelectDuration}>
|
<select value={expiresIn} onChange={this.handleSelectDuration}>
|
||||||
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
<option value={300}>{intl.formatMessage(messages.minutes, { number: 5 })}</option>
|
||||||
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
<option value={1800}>{intl.formatMessage(messages.minutes, { number: 30 })}</option>
|
||||||
|
|
|
@ -11,13 +11,13 @@ import Icon from 'mastodon/components/icon';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Post to public timelines' },
|
public_long: { id: 'privacy.public.long', defaultMessage: 'Visible for all, shown in public timelines' },
|
||||||
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
unlisted_short: { id: 'privacy.unlisted.short', defaultMessage: 'Unlisted' },
|
||||||
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Do not show in public timelines' },
|
unlisted_long: { id: 'privacy.unlisted.long', defaultMessage: 'Visible for all, but not in public timelines' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers-only' },
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Post to followers only' },
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' },
|
||||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Post to mentioned users only' },
|
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -69,7 +69,7 @@ class Blocks extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{domains.map(domain =>
|
{domains.map(domain =>
|
||||||
<DomainContainer key={domain} domain={domain} />
|
<DomainContainer key={domain} domain={domain} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -79,7 +79,7 @@ class Favourites extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -68,7 +68,7 @@ class FollowRequests extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{accountIds.map(id =>
|
{accountIds.map(id =>
|
||||||
<AccountAuthorizeContainer key={id} id={id} />
|
<AccountAuthorizeContainer key={id} id={id} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -93,7 +93,7 @@ class Followers extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{blockedBy ? [] : accountIds.map(id =>
|
{blockedBy ? [] : accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -93,7 +93,7 @@ class Following extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{blockedBy ? [] : accountIds.map(id =>
|
{blockedBy ? [] : accountIds.map(id =>
|
||||||
<AccountContainer key={id} id={id} withNote={false} />
|
<AccountContainer key={id} id={id} withNote={false} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -95,6 +95,10 @@ class Content extends ImmutablePureComponent {
|
||||||
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
} else if (link.textContent[0] === '#' || (link.previousSibling && link.previousSibling.textContent && link.previousSibling.textContent[link.previousSibling.textContent.length - 1] === '#')) {
|
||||||
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
link.addEventListener('click', this.onHashtagClick.bind(this, link.text), false);
|
||||||
} else {
|
} else {
|
||||||
|
let status = this.props.announcement.get('statuses').find(item => link.href === item.get('url'));
|
||||||
|
if (status) {
|
||||||
|
link.addEventListener('click', this.onStatusClick.bind(this, status), false);
|
||||||
|
}
|
||||||
link.setAttribute('title', link.href);
|
link.setAttribute('title', link.href);
|
||||||
link.classList.add('unhandled-link');
|
link.classList.add('unhandled-link');
|
||||||
}
|
}
|
||||||
|
@ -120,6 +124,13 @@ class Content extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onStatusClick = (status, e) => {
|
||||||
|
if (this.context.router && e.button === 0 && !(e.ctrlKey || e.metaKey)) {
|
||||||
|
e.preventDefault();
|
||||||
|
this.context.router.history.push(`/statuses/${status.get('id')}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleEmojiMouseEnter = ({ target }) => {
|
handleEmojiMouseEnter = ({ target }) => {
|
||||||
target.src = target.getAttribute('data-original');
|
target.src = target.getAttribute('data-original');
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,20 +106,20 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
|
|
||||||
if (profile_directory) {
|
if (profile_directory) {
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />
|
<ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
|
||||||
);
|
);
|
||||||
|
|
||||||
height += 48;
|
height += 48;
|
||||||
}
|
}
|
||||||
|
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnSubheading key={i++} text={intl.formatMessage(messages.personal)} />
|
<ColumnSubheading key={i++} text={intl.formatMessage(messages.personal)} />,
|
||||||
);
|
);
|
||||||
|
|
||||||
height += 34;
|
height += 34;
|
||||||
} else if (profile_directory) {
|
} else if (profile_directory) {
|
||||||
navItems.push(
|
navItems.push(
|
||||||
<ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />
|
<ColumnLink key={i++} icon='address-book' text={intl.formatMessage(messages.profile_directory)} to='/directory' />,
|
||||||
);
|
);
|
||||||
|
|
||||||
height += 48;
|
height += 48;
|
||||||
|
@ -129,7 +129,7 @@ class GettingStarted extends ImmutablePureComponent {
|
||||||
<ColumnLink key={i++} icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
|
<ColumnLink key={i++} icon='envelope' text={intl.formatMessage(messages.direct)} to='/timelines/direct' />,
|
||||||
<ColumnLink key={i++} icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
<ColumnLink key={i++} icon='bookmark' text={intl.formatMessage(messages.bookmarks)} to='/bookmarks' />,
|
||||||
<ColumnLink key={i++} icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
<ColumnLink key={i++} icon='star' text={intl.formatMessage(messages.favourites)} to='/favourites' />,
|
||||||
<ColumnLink key={i++} icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />
|
<ColumnLink key={i++} icon='list-ul' text={intl.formatMessage(messages.lists)} to='/lists' />,
|
||||||
);
|
);
|
||||||
|
|
||||||
height += 48*4;
|
height += 48*4;
|
||||||
|
|
|
@ -74,7 +74,7 @@ class Lists extends ImmutablePureComponent {
|
||||||
bindToDocument={!multiColumn}
|
bindToDocument={!multiColumn}
|
||||||
>
|
>
|
||||||
{lists.map(list =>
|
{lists.map(list =>
|
||||||
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />
|
<ColumnLink key={list.get('id')} to={`/timelines/list/${list.get('id')}`} icon='list-ul' text={list.get('title')} />,
|
||||||
)}
|
)}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue