Merge remote-tracking branch 'parent/main' into upstream-20231116

This commit is contained in:
KMY 2023-11-14 09:14:19 +09:00
commit 0704829a9b
80 changed files with 1483 additions and 1117 deletions

3
.watchmanconfig Normal file
View file

@ -0,0 +1,3 @@
{
"ignore_dirs": ["node_modules/", "public/"]
}

View file

@ -39,50 +39,51 @@ GIT
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
actioncable (7.1.1) actioncable (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6) zeitwerk (~> 2.6)
actionmailbox (7.1.1) actionmailbox (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
activejob (= 7.1.1) activejob (= 7.1.2)
activerecord (= 7.1.1) activerecord (= 7.1.2)
activestorage (= 7.1.1) activestorage (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
mail (>= 2.7.1) mail (>= 2.7.1)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
actionmailer (7.1.1) actionmailer (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
actionview (= 7.1.1) actionview (= 7.1.2)
activejob (= 7.1.1) activejob (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
net-imap net-imap
net-pop net-pop
net-smtp net-smtp
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
actionpack (7.1.1) actionpack (7.1.2)
actionview (= 7.1.1) actionview (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4) rack (>= 2.2.4)
rack-session (>= 1.0.1) rack-session (>= 1.0.1)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6) rails-html-sanitizer (~> 1.6)
actiontext (7.1.1) actiontext (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
activerecord (= 7.1.1) activerecord (= 7.1.2)
activestorage (= 7.1.1) activestorage (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
globalid (>= 0.6.0) globalid (>= 0.6.0)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (7.1.1) actionview (7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.11) erubi (~> 1.11)
rails-dom-testing (~> 2.2) rails-dom-testing (~> 2.2)
@ -92,22 +93,22 @@ GEM
activemodel (>= 4.1) activemodel (>= 4.1)
case_transform (>= 0.2) case_transform (>= 0.2)
jsonapi-renderer (>= 0.1.1.beta1, < 0.3) jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
activejob (7.1.1) activejob (7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (7.1.1) activemodel (7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
activerecord (7.1.1) activerecord (7.1.2)
activemodel (= 7.1.1) activemodel (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
timeout (>= 0.4.0) timeout (>= 0.4.0)
activestorage (7.1.1) activestorage (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
activejob (= 7.1.1) activejob (= 7.1.2)
activerecord (= 7.1.1) activerecord (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
marcel (~> 1.0) marcel (~> 1.0)
activesupport (7.1.1) activesupport (7.1.2)
base64 base64
bigdecimal bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
@ -218,7 +219,7 @@ GEM
activerecord (>= 5.a) activerecord (>= 5.a)
database_cleaner-core (~> 2.0.0) database_cleaner-core (~> 2.0.0)
database_cleaner-core (2.0.1) database_cleaner-core (2.0.1)
date (3.3.3) date (3.3.4)
debug_inspector (1.1.0) debug_inspector (1.1.0)
devise (4.9.3) devise (4.9.3)
bcrypt (~> 3.0) bcrypt (~> 3.0)
@ -369,7 +370,7 @@ GEM
terminal-table (>= 1.5.1) terminal-table (>= 1.5.1)
idn-ruby (0.1.5) idn-ruby (0.1.5)
io-console (0.6.0) io-console (0.6.0)
irb (1.8.1) irb (1.8.3)
rdoc rdoc
reline (>= 0.3.8) reline (>= 0.3.8)
jmespath (1.6.2) jmespath (1.6.2)
@ -462,13 +463,13 @@ GEM
uri uri
net-http-persistent (4.0.2) net-http-persistent (4.0.2)
connection_pool (~> 2.2) connection_pool (~> 2.2)
net-imap (0.4.1) net-imap (0.4.4)
date date
net-protocol net-protocol
net-ldap (0.18.0) net-ldap (0.18.0)
net-pop (0.1.2) net-pop (0.1.2)
net-protocol net-protocol
net-protocol (0.2.1) net-protocol (0.2.2)
timeout timeout
net-smtp (0.4.0) net-smtp (0.4.0)
net-protocol net-protocol
@ -526,7 +527,7 @@ GEM
net-smtp net-smtp
premailer (~> 1.7, >= 1.7.9) premailer (~> 1.7, >= 1.7.9)
private_address_check (0.5.0) private_address_check (0.5.0)
psych (5.1.1) psych (5.1.1.1)
stringio stringio
public_suffix (5.0.3) public_suffix (5.0.3)
puma (6.4.0) puma (6.4.0)
@ -557,20 +558,20 @@ GEM
rackup (1.0.0) rackup (1.0.0)
rack (< 3) rack (< 3)
webrick webrick
rails (7.1.1) rails (7.1.2)
actioncable (= 7.1.1) actioncable (= 7.1.2)
actionmailbox (= 7.1.1) actionmailbox (= 7.1.2)
actionmailer (= 7.1.1) actionmailer (= 7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
actiontext (= 7.1.1) actiontext (= 7.1.2)
actionview (= 7.1.1) actionview (= 7.1.2)
activejob (= 7.1.1) activejob (= 7.1.2)
activemodel (= 7.1.1) activemodel (= 7.1.2)
activerecord (= 7.1.1) activerecord (= 7.1.2)
activestorage (= 7.1.1) activestorage (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
bundler (>= 1.15.0) bundler (>= 1.15.0)
railties (= 7.1.1) railties (= 7.1.2)
rails-controller-testing (1.0.5) rails-controller-testing (1.0.5)
actionpack (>= 5.0.1.rc1) actionpack (>= 5.0.1.rc1)
actionview (>= 5.0.1.rc1) actionview (>= 5.0.1.rc1)
@ -585,9 +586,9 @@ GEM
rails-i18n (7.0.8) rails-i18n (7.0.8)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8) railties (>= 6.0.0, < 8)
railties (7.1.1) railties (7.1.2)
actionpack (= 7.1.1) actionpack (= 7.1.2)
activesupport (= 7.1.1) activesupport (= 7.1.2)
irb irb
rackup (>= 1.0.0) rackup (>= 1.0.0)
rake (>= 12.2) rake (>= 12.2)
@ -739,7 +740,7 @@ GEM
statsd-ruby (1.5.0) statsd-ruby (1.5.0)
stoplight (3.0.2) stoplight (3.0.2)
redlock (~> 1.0) redlock (~> 1.0)
stringio (3.0.8) stringio (3.0.9)
strong_migrations (1.6.4) strong_migrations (1.6.4)
activerecord (>= 5.2) activerecord (>= 5.2)
swd (1.3.0) swd (1.3.0)
@ -755,7 +756,7 @@ GEM
test-prof (1.2.3) test-prof (1.2.3)
thor (1.3.0) thor (1.3.0)
tilt (2.3.0) tilt (2.3.0)
timeout (0.4.0) timeout (0.4.1)
tpm-key_attestation (0.12.0) tpm-key_attestation (0.12.0)
bindata (~> 2.4) bindata (~> 2.4)
openssl (> 2.0) openssl (> 2.0)

View file

@ -144,7 +144,7 @@ class PublicStatusesIndex < Chewy::Index
index_scope ::Status.unscoped index_scope ::Status.unscoped
.kept .kept
.indexable .indexable
.includes(:media_attachments, :preloadable_poll, :preview_cards, :tags, :account) .includes(:media_attachments, :preloadable_poll, :tags, :account, preview_cards_status: :preview_card)
root date_detection: false do root date_detection: false do
field(:id, type: 'long') field(:id, type: 'long')

View file

@ -147,7 +147,6 @@ class StatusesIndex < Chewy::Index
index_scope ::Status.unscoped.kept.without_reblogs.includes( index_scope ::Status.unscoped.kept.without_reblogs.includes(
:account, :account,
:media_attachments, :media_attachments,
:preview_cards,
:local_mentioned, :local_mentioned,
:local_favorited, :local_favorited,
:local_reblogged, :local_reblogged,
@ -155,6 +154,7 @@ class StatusesIndex < Chewy::Index
:local_emoji_reacted, :local_emoji_reacted,
:tags, :tags,
:local_referenced, :local_referenced,
preview_cards_status: :preview_card,
preloadable_poll: :local_voters preloadable_poll: :local_voters
), ),
delete_if: lambda { |status| delete_if: lambda { |status|

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::AccountsController < Api::BaseController class Api::V1::AccountsController < Api::BaseController
include RegistrationHelper
before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute] before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :remove_from_followers, :block, :unblock, :mute, :unmute]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers] before_action -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute] before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
@ -90,18 +92,14 @@ class Api::V1::AccountsController < Api::BaseController
end end
def account_params def account_params
params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone) params.permit(:username, :email, :password, :agreement, :locale, :reason, :time_zone, :invite_code)
end
def invite
Invite.find_by(code: params[:invite_code]) if params[:invite_code].present?
end end
def check_enabled_registrations def check_enabled_registrations
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations? forbidden unless allowed_registration?(request.remote_ip, invite)
end
def allowed_registrations?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end end
end end

View file

@ -41,10 +41,10 @@ class Api::V1::ConversationsController < Api::BaseController
account: :account_stat, account: :account_stat,
last_status: [ last_status: [
:media_attachments, :media_attachments,
:preview_cards,
:status_stat, :status_stat,
:tags, :tags,
{ {
preview_cards_status: :preview_card,
active_mentions: [account: :account_stat], active_mentions: [account: :account_stat],
account: :account_stat, account: :account_stat,
}, },

View file

@ -0,0 +1,30 @@
# frozen_string_literal: true
class Api::V1::InvitesController < Api::BaseController
include RegistrationHelper
skip_before_action :require_authenticated_user!
skip_around_action :set_locale
before_action :set_invite
before_action :check_enabled_registrations!
# Override `current_user` to avoid reading session cookies
def current_user; end
def show
render json: { invite_code: params[:invite_code], instance_api_url: api_v2_instance_url }, status: 200
end
private
def set_invite
@invite = Invite.find_by!(code: params[:invite_code])
end
def check_enabled_registrations!
return render json: { error: I18n.t('invites.invalid') }, status: 401 unless @invite.valid_for_use?
raise Mastodon::NotPermittedError unless allowed_registration?(request.remote_ip, @invite)
end
end

View file

@ -0,0 +1,16 @@
# frozen_string_literal: true
class Api::V1::Statuses::BaseController < Api::BaseController
include Authorization
before_action :set_status
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View file

@ -1,11 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::BookmarksController < Api::BaseController class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' } before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user! before_action :require_user!
before_action :set_status, only: [:create] skip_before_action :set_status, only: [:destroy]
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)
@ -28,13 +26,4 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
not_found not_found
end end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end end

View file

@ -1,10 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController class Api::V1::Statuses::FavouritedByAccountsController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' } before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@ -61,13 +58,6 @@ class Api::V1::Statuses::FavouritedByAccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit).permit(:limit).merge(core_params)
end end

View file

@ -1,11 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::FavouritesController < Api::BaseController class Api::V1::Statuses::FavouritesController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:favourites' } before_action -> { doorkeeper_authorize! :write, :'write:favourites' }
before_action :require_user! before_action :require_user!
before_action :set_status, only: [:create] skip_before_action :set_status, only: [:destroy]
def create def create
FavouriteService.new.call(current_account, @status) FavouriteService.new.call(current_account, @status)
@ -30,13 +28,4 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
rescue Mastodon::NotPermittedError rescue Mastodon::NotPermittedError
not_found not_found
end end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end end

View file

@ -1,10 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::HistoriesController < Api::BaseController class Api::V1::Statuses::HistoriesController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:statuses' } before_action -> { authorize_if_got_token! :read, :'read:statuses' }
before_action :set_status
def show def show
cache_if_unauthenticated! cache_if_unauthenticated!
@ -16,11 +13,4 @@ class Api::V1::Statuses::HistoriesController < Api::BaseController
def status_edits def status_edits
@status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)] @status.edits.includes(:account, status: [:account]).to_a.presence || [@status.build_snapshot(at_time: @status.edited_at || @status.created_at)]
end end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end end

View file

@ -1,11 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::MutesController < Api::BaseController class Api::V1::Statuses::MutesController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:mutes' } before_action -> { doorkeeper_authorize! :write, :'write:mutes' }
before_action :require_user! before_action :require_user!
before_action :set_status
before_action :set_conversation before_action :set_conversation
def create def create
@ -24,13 +21,6 @@ class Api::V1::Statuses::MutesController < Api::BaseController
private private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_conversation def set_conversation
@conversation = @status.conversation @conversation = @status.conversation
raise Mastodon::ValidationError if @conversation.nil? raise Mastodon::ValidationError if @conversation.nil?

View file

@ -1,11 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::PinsController < Api::BaseController class Api::V1::Statuses::PinsController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :write, :'write:accounts' } before_action -> { doorkeeper_authorize! :write, :'write:accounts' }
before_action :require_user! before_action :require_user!
before_action :set_status
def create def create
StatusPin.create!(account: current_account, status: @status) StatusPin.create!(account: current_account, status: @status)
@ -26,10 +23,6 @@ class Api::V1::Statuses::PinsController < Api::BaseController
private private
def set_status
@status = Status.find(params[:status_id])
end
def distribute_add_activity! def distribute_add_activity!
json = ActiveModelSerializers::SerializableResource.new( json = ActiveModelSerializers::SerializableResource.new(
@status, @status,

View file

@ -1,10 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController class Api::V1::Statuses::RebloggedByAccountsController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' } before_action -> { authorize_if_got_token! :read, :'read:accounts' }
before_action :set_status
after_action :insert_pagination_headers after_action :insert_pagination_headers
def index def index
@ -57,13 +54,6 @@ class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
@accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT) @accounts.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
end end
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def pagination_params(core_params) def pagination_params(core_params)
params.slice(:limit).permit(:limit).merge(core_params) params.slice(:limit).permit(:limit).merge(core_params)
end end

View file

@ -1,13 +1,13 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::ReblogsController < Api::BaseController class Api::V1::Statuses::ReblogsController < Api::V1::Statuses::BaseController
include Authorization
include Redisable include Redisable
include Lockable include Lockable
before_action -> { doorkeeper_authorize! :write, :'write:statuses' } before_action -> { doorkeeper_authorize! :write, :'write:statuses' }
before_action :require_user! before_action :require_user!
before_action :set_reblog, only: [:create] before_action :set_reblog, only: [:create]
skip_before_action :set_status
override_rate_limit_headers :create, family: :statuses override_rate_limit_headers :create, family: :statuses

View file

@ -1,21 +1,9 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::SourcesController < Api::BaseController class Api::V1::Statuses::SourcesController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' } before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
def show def show
render json: @status, serializer: REST::StatusSourceSerializer render json: @status, serializer: REST::StatusSourceSerializer
end end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end end

View file

@ -1,10 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::TranslationsController < Api::BaseController class Api::V1::Statuses::TranslationsController < Api::V1::Statuses::BaseController
include Authorization
before_action -> { doorkeeper_authorize! :read, :'read:statuses' } before_action -> { doorkeeper_authorize! :read, :'read:statuses' }
before_action :set_status
before_action :set_translation before_action :set_translation
rescue_from TranslationService::NotConfiguredError, with: :not_found rescue_from TranslationService::NotConfiguredError, with: :not_found
@ -24,13 +21,6 @@ class Api::V1::Statuses::TranslationsController < Api::BaseController
private private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
def set_translation def set_translation
@translation = TranslateStatusService.new.call(@status, content_locale) @translation = TranslateStatusService.new.call(@status, content_locale)
end end

View file

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Auth::RegistrationsController < Devise::RegistrationsController class Auth::RegistrationsController < Devise::RegistrationsController
include RegistrationHelper
include RegistrationSpamConcern include RegistrationSpamConcern
layout :determine_layout layout :determine_layout
@ -82,19 +83,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end end
def check_enabled_registrations def check_enabled_registrations
redirect_to root_path if single_user_mode? || omniauth_only? || !allowed_registrations? || ip_blocked? redirect_to root_path unless allowed_registration?(request.remote_ip, @invite)
end
def allowed_registrations?
Setting.registrations_mode != 'none' || @invite&.valid_for_use?
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?
IpBlock.where(severity: :sign_up_block).where('ip >>= ?', request.remote_ip.to_s).exists?
end end
def invite_code def invite_code

View file

@ -0,0 +1,21 @@
# frozen_string_literal: true
module RegistrationHelper
extend ActiveSupport::Concern
def allowed_registration?(remote_ip, invite)
!Rails.configuration.x.single_user_mode && !omniauth_only? && (registrations_open? || invite&.valid_for_use?) && !ip_blocked?(remote_ip)
end
def registrations_open?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?(remote_ip)
IpBlock.where(severity: :sign_up_block).exists?(['ip >>= ?', remote_ip.to_s])
end
end

View file

@ -20,6 +20,8 @@ import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/s
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg'; import { ReactComponent as VisibilityIcon } from '@material-symbols/svg-600/outlined/visibility.svg';
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -426,6 +428,7 @@ class StatusActionBar extends ImmutablePureComponent {
let replyIcon; let replyIcon;
let replyIconComponent; let replyIconComponent;
let replyTitle; let replyTitle;
if (status.get('in_reply_to_id', null) === null) { if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply'; replyIcon = 'reply';
replyIconComponent = ReplyIcon; replyIconComponent = ReplyIcon;
@ -438,15 +441,20 @@ class StatusActionBar extends ImmutablePureComponent {
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private'; const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
let reblogTitle = ''; let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) { if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private); reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) { } else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog); reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) { } else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private); reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else { } else {
reblogTitle = intl.formatMessage(messages.cannot_reblog); reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
} }
const filterButton = this.props.onFilter && ( const filterButton = this.props.onFilter && (
@ -472,7 +480,7 @@ class StatusActionBar extends ImmutablePureComponent {
return ( return (
<div className='status__action-bar'> <div className='status__action-bar'>
<IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} /> <IconButton className='status__action-bar__button' title={replyTitle} icon={isReply ? 'reply' : replyIcon} iconComponent={isReply ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} counter={status.get('replies_count')} />
<IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} /> <IconButton className={classNames('status__action-bar__button', { reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} counter={withCounters ? status.get('reblogs_count') : undefined} />
<IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} /> <IconButton className='status__action-bar__button star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} counter={withCounters ? status.get('favourites_count') : undefined} />
<IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /> <IconButton className='status__action-bar__button bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} />
{emojiPickerDropdown} {emojiPickerDropdown}

View file

@ -19,6 +19,8 @@ import { ReactComponent as ReplyAllIcon } from '@material-symbols/svg-600/outlin
import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg'; import { ReactComponent as StarIcon } from '@material-symbols/svg-600/outlined/star-fill.svg';
import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg'; import { ReactComponent as StarBorderIcon } from '@material-symbols/svg-600/outlined/star.svg';
import { ReactComponent as RepeatDisabledIcon } from 'mastodon/../svg-icons/repeat_disabled.svg';
import { ReactComponent as RepeatPrivateIcon } from 'mastodon/../svg-icons/repeat_private.svg';
import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions'; import { PERMISSION_MANAGE_USERS, PERMISSION_MANAGE_FEDERATION } from 'mastodon/permissions';
import { WithRouterPropTypes } from 'mastodon/utils/react_router'; import { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -337,6 +339,7 @@ class ActionBar extends PureComponent {
let replyIcon; let replyIcon;
let replyIconComponent; let replyIconComponent;
if (status.get('in_reply_to_id', null) === null) { if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply'; replyIcon = 'reply';
replyIconComponent = ReplyIcon; replyIconComponent = ReplyIcon;
@ -347,15 +350,20 @@ class ActionBar extends PureComponent {
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private'; const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
let reblogTitle; let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) { if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private); reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) { } else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog); reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) { } else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private); reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else { } else {
reblogTitle = intl.formatMessage(messages.cannot_reblog); reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
} }
const emojiReactionAvailableServer = !isHideItem('emoji_reaction_unavailable_server') || status.get('emoji_reaction_available_server'); const emojiReactionAvailableServer = !isHideItem('emoji_reaction_unavailable_server') || status.get('emoji_reaction_available_server');
@ -375,7 +383,7 @@ class ActionBar extends PureComponent {
return ( return (
<div className='detailed-status__action-bar'> <div className='detailed-status__action-bar'>
<div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div> <div className='detailed-status__button'><IconButton title={intl.formatMessage(messages.reply)} icon={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? 'reply' : replyIcon} iconComponent={status.get('in_reply_to_account_id') === status.getIn(['account', 'id']) ? ReplyIcon : replyIconComponent} onClick={this.handleReplyClick} /></div>
<div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={RepeatIcon} onClick={this.handleReblogClick} /></div> <div className='detailed-status__button'><IconButton className={classNames({ reblogPrivate })} disabled={!publicStatus && !reblogPrivate} active={status.get('reblogged')} title={reblogTitle} icon='retweet' iconComponent={reblogIconComponent} onClick={this.handleReblogClick} /></div>
<div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div> <div className='detailed-status__button'><IconButton className='star-icon' animate active={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' iconComponent={status.get('favourited') ? StarIcon : StarBorderIcon} onClick={this.handleFavouriteClick} /></div>
<div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div> <div className='detailed-status__button'><IconButton className='bookmark-icon' disabled={!signedIn} active={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' iconComponent={status.get('bookmarked') ? BookmarkIcon : BookmarkBorderIcon} onClick={this.handleBookmarkClick} /></div>
{emojiPickerDropdown} {emojiPickerDropdown}

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Вынікі пошуку", "emoji_button.search_results": "Вынікі пошуку",
"emoji_button.symbols": "Сімвалы", "emoji_button.symbols": "Сімвалы",
"emoji_button.travel": "Падарожжы і месцы", "emoji_button.travel": "Падарожжы і месцы",
"empty_column.account_hides_collections": "Гэты карыстальнік вырашыў схаваць гэтую інфармацыю",
"empty_column.account_suspended": "Уліковы запіс прыпынены", "empty_column.account_suspended": "Уліковы запіс прыпынены",
"empty_column.account_timeline": "Тут няма допісаў!", "empty_column.account_timeline": "Тут няма допісаў!",
"empty_column.account_unavailable": "Профіль недаступны", "empty_column.account_unavailable": "Профіль недаступны",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Otsitulemused", "emoji_button.search_results": "Otsitulemused",
"emoji_button.symbols": "Sümbolid", "emoji_button.symbols": "Sümbolid",
"emoji_button.travel": "Reisimine & kohad", "emoji_button.travel": "Reisimine & kohad",
"empty_column.account_hides_collections": "See kasutaja otsustas mitte teha seda infot saadavaks",
"empty_column.account_suspended": "Konto kustutatud", "empty_column.account_suspended": "Konto kustutatud",
"empty_column.account_timeline": "Siin postitusi ei ole!", "empty_column.account_timeline": "Siin postitusi ei ole!",
"empty_column.account_unavailable": "Profiil pole saadaval", "empty_column.account_unavailable": "Profiil pole saadaval",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "نتایج جست‌وجو", "emoji_button.search_results": "نتایج جست‌وجو",
"emoji_button.symbols": "نمادها", "emoji_button.symbols": "نمادها",
"emoji_button.travel": "سفر و مکان", "emoji_button.travel": "سفر و مکان",
"empty_column.account_hides_collections": "کاربر خواسته که این اطّلاعات در دسترس نباشند",
"empty_column.account_suspended": "حساب معلق شد", "empty_column.account_suspended": "حساب معلق شد",
"empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!", "empty_column.account_timeline": "هیچ فرسته‌ای این‌جا نیست!",
"empty_column.account_unavailable": "نمایهٔ موجود نیست", "empty_column.account_unavailable": "نمایهٔ موجود نیست",
@ -358,13 +359,13 @@
"keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده", "keyboard_shortcuts.profile": "گشودن نمایهٔ نویسنده",
"keyboard_shortcuts.reply": "پاسخ به فرسته", "keyboard_shortcuts.reply": "پاسخ به فرسته",
"keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری", "keyboard_shortcuts.requests": "گشودن سیاههٔ درخواست‌های پی‌گیری",
"keyboard_shortcuts.search": "تمرکز روی جست‌وجو", "keyboard_shortcuts.search": "تمرکز روی نوار جست‌وجو",
"keyboard_shortcuts.spoilers": "نمایش/نهفتن زمینهٔ هشدار محتوا", "keyboard_shortcuts.spoilers": "نمایش/نهفتن زمینهٔ هشدار محتوا",
"keyboard_shortcuts.start": "گشودن ستون «آغاز کنید»", "keyboard_shortcuts.start": "گشودن ستون «آغاز کنید»",
"keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا", "keyboard_shortcuts.toggle_hidden": "نمایش/نهفتن نوشتهٔ پشت هشدار محتوا",
"keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه", "keyboard_shortcuts.toggle_sensitivity": "نمایش/نهفتن رسانه",
"keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید", "keyboard_shortcuts.toot": "شروع یک فرستهٔ جدید",
"keyboard_shortcuts.unfocus": "برداشتن تمرکز از نوشتن/جست‌وجو", "keyboard_shortcuts.unfocus": "برداشتن تمرکز از ناحیهٔ نوشتن یا جست‌وجو",
"keyboard_shortcuts.up": "بالا بردن در سیاهه", "keyboard_shortcuts.up": "بالا بردن در سیاهه",
"lightbox.close": "بستن", "lightbox.close": "بستن",
"lightbox.compress": "فشرده‌سازی جعبهٔ نمایش تصویر", "lightbox.compress": "فشرده‌سازی جعبهٔ نمایش تصویر",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Hakutulokset", "emoji_button.search_results": "Hakutulokset",
"emoji_button.symbols": "Symbolit", "emoji_button.symbols": "Symbolit",
"emoji_button.travel": "Matkailu ja paikat", "emoji_button.travel": "Matkailu ja paikat",
"empty_column.account_hides_collections": "Käyttäjä on päättänyt olla julkaisematta näitä tietoja",
"empty_column.account_suspended": "Tili jäädytetty", "empty_column.account_suspended": "Tili jäädytetty",
"empty_column.account_timeline": "Ei viestejä täällä.", "empty_column.account_timeline": "Ei viestejä täällä.",
"empty_column.account_unavailable": "Profiilia ei löydy", "empty_column.account_unavailable": "Profiilia ei löydy",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Leitiúrslit", "emoji_button.search_results": "Leitiúrslit",
"emoji_button.symbols": "Ímyndir", "emoji_button.symbols": "Ímyndir",
"emoji_button.travel": "Ferðing og støð", "emoji_button.travel": "Ferðing og støð",
"empty_column.account_hides_collections": "Hesin brúkarin hevur valt, at hesar upplýsingarnar ikki skulu vera tøkar",
"empty_column.account_suspended": "Kontan gjørd óvirkin", "empty_column.account_suspended": "Kontan gjørd óvirkin",
"empty_column.account_timeline": "Einki uppslag her!", "empty_column.account_timeline": "Einki uppslag her!",
"empty_column.account_unavailable": "Vangin er ikki tøkur", "empty_column.account_unavailable": "Vangin er ikki tøkur",

View file

@ -1,8 +1,11 @@
{ {
"account.add_or_remove_from_list": "Tinye ma ọ bụ Wepu na ndepụta", "account.add_or_remove_from_list": "Tinye ma ọ bụ Wepu na ndepụta",
"account.badges.bot": "Bot", "account.badges.bot": "Bot",
"account.badges.group": "Otù",
"account.cancel_follow_request": "Withdraw follow request", "account.cancel_follow_request": "Withdraw follow request",
"account.follow": "Soro", "account.follow": "Soro",
"account.followers": "Ndị na-eso",
"account.following": "Na-eso",
"account.follows_you": "Na-eso gị", "account.follows_you": "Na-eso gị",
"account.mute": "Mee ogbi @{name}", "account.mute": "Mee ogbi @{name}",
"account.unfollow": "Kwụsị iso", "account.unfollow": "Kwụsị iso",
@ -11,16 +14,20 @@
"audio.hide": "Zoo ụda", "audio.hide": "Zoo ụda",
"bundle_column_error.retry": "Nwaa ọzọ", "bundle_column_error.retry": "Nwaa ọzọ",
"bundle_column_error.routing.title": "404", "bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Mechie",
"bundle_modal_error.retry": "Nwaa ọzọ", "bundle_modal_error.retry": "Nwaa ọzọ",
"column.about": "Maka", "column.about": "Maka",
"column.blocks": "Ojiarụ egbochiri", "column.blocks": "Ojiarụ egbochiri",
"column.bookmarks": "Ebenrụtụakā", "column.bookmarks": "Ebenrụtụakā",
"column.home": "Be", "column.home": "Be",
"column.lists": "Ndepụta",
"column.pins": "Pinned post", "column.pins": "Pinned post",
"column_header.pin": "Gbado na profaịlụ gị",
"column_subheading.settings": "Mwube", "column_subheading.settings": "Mwube",
"community.column_settings.media_only": "Media only", "community.column_settings.media_only": "Media only",
"compose.language.change": "Gbanwee asụsụ", "compose.language.change": "Gbanwee asụsụ",
"compose.language.search": "Chọọ asụsụ...", "compose.language.search": "Chọọ asụsụ...",
"compose.published.open": "Mepe",
"compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.", "compose_form.encryption_warning": "Posts on Mastodon are not end-to-end encrypted. Do not share any dangerous information over Mastodon.",
"compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.", "compose_form.hashtag_warning": "This post won't be listed under any hashtag as it is unlisted. Only public posts can be searched by hashtag.",
"compose_form.placeholder": "What is on your mind?", "compose_form.placeholder": "What is on your mind?",
@ -32,7 +39,10 @@
"confirmations.delete.message": "Are you sure you want to delete this status?", "confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Hichapụ", "confirmations.delete_list.confirm": "Hichapụ",
"confirmations.domain_block.confirm": "Hide entire domain", "confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.edit.confirm": "Dezie",
"confirmations.mute.confirm": "Mee ogbi",
"confirmations.reply.confirm": "Zaa", "confirmations.reply.confirm": "Zaa",
"confirmations.unfollow.confirm": "Kwụsị iso",
"conversation.delete": "Hichapụ nkata", "conversation.delete": "Hichapụ nkata",
"dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_links": "These news stories are being talked about by people on this and other servers of the decentralized network right now.",
"dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.", "dismissable_banner.explore_tags": "These hashtags are gaining traction among people on this and other servers of the decentralized network right now.",
@ -76,6 +86,7 @@
"keyboard_shortcuts.unfocus": "to un-focus compose textarea/search", "keyboard_shortcuts.unfocus": "to un-focus compose textarea/search",
"keyboard_shortcuts.up": "to move up in the list", "keyboard_shortcuts.up": "to move up in the list",
"lists.delete": "Hichapụ ndepụta", "lists.delete": "Hichapụ ndepụta",
"lists.edit": "Dezie ndepụta",
"lists.subheading": "Ndepụta gị", "lists.subheading": "Ndepụta gị",
"loading_indicator.label": "Na-adọnye...", "loading_indicator.label": "Na-adọnye...",
"navigation_bar.about": "Maka", "navigation_bar.about": "Maka",
@ -100,20 +111,27 @@
"privacy.change": "Adjust status privacy", "privacy.change": "Adjust status privacy",
"privacy.direct.short": "Direct", "privacy.direct.short": "Direct",
"privacy.private.short": "Followers-only", "privacy.private.short": "Followers-only",
"relative_time.full.just_now": "kịta",
"relative_time.just_now": "kịta", "relative_time.just_now": "kịta",
"relative_time.today": "taa", "relative_time.today": "taa",
"reply_indicator.cancel": "Kagbuo", "reply_indicator.cancel": "Kagbuo",
"report.categories.other": "Ọzọ", "report.categories.other": "Ọzọ",
"report.categories.spam": "Nzipụ Ozièlètrọniìk Nkeāchọghị",
"report.mute": "Mee ogbi",
"report.placeholder": "Type or paste additional comments", "report.placeholder": "Type or paste additional comments",
"report.submit": "Submit report", "report.submit": "Submit report",
"report.target": "Report {target}", "report.target": "Report {target}",
"report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached", "report_notification.attached_statuses": "{count, plural, one {# post} other {# posts}} attached",
"report_notification.categories.other": "Ọzọ",
"search.placeholder": "Chọọ",
"server_banner.active_users": "ojiarụ dị ìrè", "server_banner.active_users": "ojiarụ dị ìrè",
"server_banner.learn_more": "Mụtakwuo",
"sign_in_banner.sign_in": "Sign in", "sign_in_banner.sign_in": "Sign in",
"status.admin_status": "Open this status in the moderation interface", "status.admin_status": "Open this status in the moderation interface",
"status.bookmark": "Kee ebenrụtụakā", "status.bookmark": "Kee ebenrụtụakā",
"status.copy": "Copy link to status", "status.copy": "Copy link to status",
"status.delete": "Hichapụ", "status.delete": "Hichapụ",
"status.edit": "Dezie",
"status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}", "status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}",
"status.open": "Expand this status", "status.open": "Expand this status",
"status.remove_bookmark": "Wepu ebenrụtụakā", "status.remove_bookmark": "Wepu ebenrụtụakā",

View file

@ -706,8 +706,8 @@
"search.no_recent_searches": "検索履歴はありません", "search.no_recent_searches": "検索履歴はありません",
"search.placeholder": "検索", "search.placeholder": "検索",
"search.quick_action.account_search": "{x}に該当するプロフィール", "search.quick_action.account_search": "{x}に該当するプロフィール",
"search.quick_action.go_to_account": "{x}のプロフィールを見る", "search.quick_action.go_to_account": "プロフィール {x} を見る",
"search.quick_action.go_to_hashtag": "{x}に該当するハッシュタグ", "search.quick_action.go_to_hashtag": "ハッシュタグ {x} を見る",
"search.quick_action.open_url": "MastodonでURLを開く", "search.quick_action.open_url": "MastodonでURLを開く",
"search.quick_action.status_search": "{x}に該当する投稿", "search.quick_action.status_search": "{x}に該当する投稿",
"search.search_or_paste": "検索またはURLを入力", "search.search_or_paste": "検索またはURLを入力",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "검색 결과", "emoji_button.search_results": "검색 결과",
"emoji_button.symbols": "기호", "emoji_button.symbols": "기호",
"emoji_button.travel": "여행과 장소", "emoji_button.travel": "여행과 장소",
"empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다",
"empty_column.account_suspended": "계정 정지됨", "empty_column.account_suspended": "계정 정지됨",
"empty_column.account_timeline": "이곳에는 게시물이 없습니다!", "empty_column.account_timeline": "이곳에는 게시물이 없습니다!",
"empty_column.account_unavailable": "프로필 사용 불가", "empty_column.account_unavailable": "프로필 사용 불가",

View file

@ -15,7 +15,7 @@
"account.add_or_remove_from_list": "Pridėti arba ištrinti iš sąrašų", "account.add_or_remove_from_list": "Pridėti arba ištrinti iš sąrašų",
"account.badges.bot": "Automatizuotas", "account.badges.bot": "Automatizuotas",
"account.badges.group": "Grupė", "account.badges.group": "Grupė",
"account.block": "Užblokuoti @{name}", "account.block": "Blokuoti @{name}",
"account.block_domain": "Blokuoti domeną {domain}", "account.block_domain": "Blokuoti domeną {domain}",
"account.block_short": "Blokuoti", "account.block_short": "Blokuoti",
"account.blocked": "Užblokuota", "account.blocked": "Užblokuota",
@ -25,11 +25,20 @@
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia", "account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
"account.domain_blocked": "Užblokuotas domenas", "account.domain_blocked": "Užblokuotas domenas",
"account.edit_profile": "Redaguoti profilį", "account.edit_profile": "Redaguoti profilį",
"account.enable_notifications": "Pranešti man, kai @{name} paskelbia",
"account.featured_tags.last_status_at": "Paskutinį kartą paskelbta {date}",
"account.featured_tags.last_status_never": "Nėra įrašų",
"account.follow": "Sekti", "account.follow": "Sekti",
"account.follows_you": "Seka jus", "account.followers": "Sekėjai",
"account.followers.empty": "Šio naudotojo dar niekas neseka.",
"account.followers_counter": "{count, plural, one {{counter} sekėjas (-a)} few {{counter} sekėjai} many {{counter} sekėjo} other {{counter} sekėjų}}",
"account.following": "Seka",
"account.follows.empty": "Šis naudotojas (-a) dar nieko neseka.",
"account.follows_you": "Seka tave",
"account.go_to_profile": "Eiti į profilį", "account.go_to_profile": "Eiti į profilį",
"account.in_memoriam": "Atminimui.", "account.in_memoriam": "Atminimui.",
"account.joined_short": "Prisijungė", "account.joined_short": "Prisijungė",
"account.languages": "Keisti prenumeruojamas kalbas",
"account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.", "account.locked_info": "Šios paskyros privatumo būsena nustatyta kaip užrakinta. Savininkas (-ė) rankiniu būdu peržiūri, kas gali sekti.",
"account.media": "Medija", "account.media": "Medija",
"account.mute": "Užtildyti @{name}", "account.mute": "Užtildyti @{name}",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Søkeresultat", "emoji_button.search_results": "Søkeresultat",
"emoji_button.symbols": "Symboler", "emoji_button.symbols": "Symboler",
"emoji_button.travel": "Reise & steder", "emoji_button.travel": "Reise & steder",
"empty_column.account_hides_collections": "Denne brukeren har valgt å ikke gjøre denne informasjonen tilgjengelig",
"empty_column.account_suspended": "Kontoen er suspendert", "empty_column.account_suspended": "Kontoen er suspendert",
"empty_column.account_timeline": "Ingen innlegg her!", "empty_column.account_timeline": "Ingen innlegg her!",
"empty_column.account_unavailable": "Profilen er utilgjengelig", "empty_column.account_unavailable": "Profilen er utilgjengelig",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Resultado da pesquisa", "emoji_button.search_results": "Resultado da pesquisa",
"emoji_button.symbols": "Símbolos", "emoji_button.symbols": "Símbolos",
"emoji_button.travel": "Viagem e Lugares", "emoji_button.travel": "Viagem e Lugares",
"empty_column.account_hides_collections": "A pessoa optou por não disponibilizar esta informação",
"empty_column.account_suspended": "Conta suspensa", "empty_column.account_suspended": "Conta suspensa",
"empty_column.account_timeline": "Nada aqui.", "empty_column.account_timeline": "Nada aqui.",
"empty_column.account_unavailable": "Perfil indisponível", "empty_column.account_unavailable": "Perfil indisponível",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Resultados da pesquisa", "emoji_button.search_results": "Resultados da pesquisa",
"emoji_button.symbols": "Símbolos", "emoji_button.symbols": "Símbolos",
"emoji_button.travel": "Viagens & Lugares", "emoji_button.travel": "Viagens & Lugares",
"empty_column.account_hides_collections": "Este utilizador escolheu não disponibilizar esta informação",
"empty_column.account_suspended": "Conta suspensa", "empty_column.account_suspended": "Conta suspensa",
"empty_column.account_timeline": "Sem publicações por aqui!", "empty_column.account_timeline": "Sem publicações por aqui!",
"empty_column.account_unavailable": "Perfil indisponível", "empty_column.account_unavailable": "Perfil indisponível",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Kết quả tìm kiếm", "emoji_button.search_results": "Kết quả tìm kiếm",
"emoji_button.symbols": "Biểu tượng", "emoji_button.symbols": "Biểu tượng",
"emoji_button.travel": "Du lịch", "emoji_button.travel": "Du lịch",
"empty_column.account_hides_collections": "Người này đã chọn ẩn thông tin",
"empty_column.account_suspended": "Tài khoản vô hiệu hóa", "empty_column.account_suspended": "Tài khoản vô hiệu hóa",
"empty_column.account_timeline": "Chưa có tút nào!", "empty_column.account_timeline": "Chưa có tút nào!",
"empty_column.account_unavailable": "Tài khoản bị đình chỉ", "empty_column.account_unavailable": "Tài khoản bị đình chỉ",

View file

@ -5508,6 +5508,7 @@ a.status-card {
.modal-root__modal { .modal-root__modal {
pointer-events: auto; pointer-events: auto;
user-select: text;
display: flex; display: flex;
} }

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 13V17.8787L17 15.8787V13H19Z"/>
<path d="M2.41421 2.70711L1 4.12132L5 8.12132V11H7V10.1213L13.8787 17H6.85L8.4 15.45L7 14L3 18L7 22L8.4 20.55L6.85 19H15.8787L19.3848 22.5061L20.799 21.0919L2.41421 2.70711Z"/>
<path d="M17.15 7H8.12132L6.12132 5H17.15L15.6 3.45L17 2L21 6L17 10L15.6 8.55L17.15 7Z"/>
</svg>

After

Width:  |  Height:  |  Size: 415 B

View file

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.4 15.45L7 14L3 18L7 22L8.4 20.55L6.85 19H13.5V18C13.5 17.6567 13.5638 17.3171 13.6988 17H6.85L8.4 15.45Z"/>
<path d="M5 5V11H7V7H17.15L15.6 8.55L17 10L21 6L17 2L15.6 3.45L17.15 5H5Z"/>
<path d="M16 22C15.7167 22 15.475 21.9083 15.275 21.725C15.0917 21.525 15 21.2833 15 21V18C15 17.7167 15.0917 17.4833 15.275 17.3C15.475 17.1 15.7167 17 16 17V16C16 15.45 16.1917 14.9833 16.575 14.6C16.975 14.2 17.45 14 18 14C18.55 14 19.0167 14.2 19.4 14.6C19.8 14.9833 20 15.45 20 16V17C20.2833 17 20.5167 17.1 20.7 17.3C20.9 17.4833 21 17.7167 21 18V21C21 21.2833 20.9 21.525 20.7 21.725C20.5167 21.9083 20.2833 22 20 22H16ZM17 17H19V16C19 15.7167 18.9 15.4833 18.7 15.3C18.5167 15.1 18.2833 15 18 15C17.7167 15 17.475 15.1 17.275 15.3C17.0917 15.4833 17 15.7167 17 16V17Z"/>
</svg>

After

Width:  |  Height:  |  Size: 879 B

View file

@ -77,7 +77,7 @@ class Admin::StatusBatchAction
# Can't use a transaction here because UpdateStatusService queues # Can't use a transaction here because UpdateStatusService queues
# Sidekiq jobs # Sidekiq jobs
statuses.includes(:media_attachments, :preview_cards).find_each do |status| statuses.includes(:media_attachments, preview_cards_status: :preview_card).find_each do |status|
next if status.discarded? || !(status.with_media? || status.with_preview_card?) next if status.discarded? || !(status.with_media? || status.with_preview_card?)
authorize([:admin, status], :update?) authorize([:admin, status], :update?)

View file

@ -77,7 +77,7 @@ module StatusSearchConcern
properties << 'media' if with_media? properties << 'media' if with_media?
properties << 'poll' if with_poll? properties << 'poll' if with_poll?
properties << 'link' if with_preview_card? properties << 'link' if with_preview_card?
properties << 'embed' if preview_cards.any?(&:video?) properties << 'embed' if preview_card&.video?
properties << 'sensitive' if sensitive? properties << 'sensitive' if sensitive?
properties << 'reply' if reply? properties << 'reply' if reply?
properties << 'reference' if with_status_reference? properties << 'reference' if with_status_reference?

View file

@ -50,7 +50,9 @@ class PreviewCard < ApplicationRecord
enum type: { link: 0, photo: 1, video: 2, rich: 3 } enum type: { link: 0, photo: 1, video: 2, rich: 3 }
enum link_type: { unknown: 0, article: 1 } enum link_type: { unknown: 0, article: 1 }
has_and_belongs_to_many :statuses has_many :preview_cards_statuses, dependent: :delete_all, inverse_of: :preview_card
has_many :statuses, through: :preview_cards_statuses
has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy has_one :trend, class_name: 'PreviewCardTrend', inverse_of: :preview_card, dependent: :destroy
has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false has_attached_file :image, processors: [:thumbnail, :blurhash_transcoder], styles: ->(f) { image_styles(f) }, convert_options: { all: '-quality 90 +profile "!icc,*" +set date:modify +set date:create +set date:timestamp' }, validate_media_type: false
@ -64,6 +66,9 @@ class PreviewCard < ApplicationRecord
before_save :extract_dimensions, if: :link? before_save :extract_dimensions, if: :link?
# This can be set by the status when retrieving the preview card using the join model
attr_accessor :original_url
def appropriate_for_trends? def appropriate_for_trends?
link? && article? && title.present? && description.present? && image.present? && provider_name.present? link? && article? && title.present? && description.present? && image.present? && provider_name.present?
end end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: preview_cards_statuses
#
# preview_card_id :bigint(8) not null
# status_id :bigint(8) not null
# url :string
#
class PreviewCardsStatus < ApplicationRecord
# Composite primary keys are not properly supported in Rails. However,
# we shouldn't need this anyway...
self.primary_key = nil
belongs_to :preview_card
belongs_to :status
end

View file

@ -103,8 +103,8 @@ class Status < ApplicationRecord
has_many :local_referenced, -> { merge(Account.local) }, through: :referenced_by_statuses, source: :account has_many :local_referenced, -> { merge(Account.local) }, through: :referenced_by_statuses, source: :account
has_and_belongs_to_many :tags has_and_belongs_to_many :tags
has_and_belongs_to_many :preview_cards
has_one :preview_cards_status, inverse_of: :status # Because of a composite primary key, the dependent option cannot be used
has_one :notification, as: :activity, dependent: :destroy has_one :notification, as: :activity, dependent: :destroy
has_one :status_stat, inverse_of: :status has_one :status_stat, inverse_of: :status
has_one :poll, inverse_of: :status, dependent: :destroy has_one :poll, inverse_of: :status, dependent: :destroy
@ -172,28 +172,29 @@ class Status < ApplicationRecord
# The `prepend: true` option below ensures this runs before # The `prepend: true` option below ensures this runs before
# the `dependent: destroy` callbacks remove relevant records # the `dependent: destroy` callbacks remove relevant records
before_destroy :unlink_from_conversations!, prepend: true before_destroy :unlink_from_conversations!, prepend: true
before_destroy :reset_preview_card!
cache_associated :application, cache_associated :application,
:media_attachments, :media_attachments,
:conversation, :conversation,
:status_stat, :status_stat,
:tags, :tags,
:preview_cards,
:preloadable_poll, :preloadable_poll,
:reference_objects, :reference_objects,
:scheduled_expiration_status, :scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role], account: [:account_stat, user: :role],
active_mentions: { account: :account_stat }, active_mentions: { account: :account_stat },
reblog: [ reblog: [
:application, :application,
:tags, :tags,
:preview_cards,
:media_attachments, :media_attachments,
:conversation, :conversation,
:status_stat, :status_stat,
:preloadable_poll, :preloadable_poll,
:reference_objects, :reference_objects,
:scheduled_expiration_status, :scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role], account: [:account_stat, user: :role],
active_mentions: { account: :account_stat }, active_mentions: { account: :account_stat },
], ],
@ -207,6 +208,7 @@ class Status < ApplicationRecord
:preloadable_poll, :preloadable_poll,
:reference_objects, :reference_objects,
:scheduled_expiration_status, :scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role], account: [:account_stat, user: :role],
active_mentions: { account: :account_stat }, active_mentions: { account: :account_stat },
], ],
@ -277,7 +279,11 @@ class Status < ApplicationRecord
end end
def preview_card def preview_card
preview_cards.first preview_cards_status&.preview_card&.tap { |x| x.original_url = preview_cards_status.url }
end
def reset_preview_card!
PreviewCardsStatus.where(status_id: id).delete_all
end end
def hidden? def hidden?
@ -300,7 +306,7 @@ class Status < ApplicationRecord
end end
def with_preview_card? def with_preview_card?
preview_cards.any? preview_cards_status.present?
end end
def with_poll? def with_poll?

View file

@ -54,9 +54,7 @@ class Trends::Links < Trends::Base
!(original_status.account.silenced? || status.account.silenced?) && !(original_status.account.silenced? || status.account.silenced?) &&
!(original_status.spoiler_text? || original_status.sensitive?) !(original_status.spoiler_text? || original_status.sensitive?)
original_status.preview_cards.each do |preview_card| add(original_status.preview_card, status.account_id, at_time) if original_status.preview_card&.appropriate_for_trends?
add(preview_card, status.account_id, at_time) if preview_card.appropriate_for_trends?
end
end end
def add(preview_card, account_id, at_time = Time.now.utc) def add(preview_card, account_id, at_time = Time.now.utc)

View file

@ -33,11 +33,11 @@ class Webhook < ApplicationRecord
validates :secret, presence: true, length: { minimum: 12 } validates :secret, presence: true, length: { minimum: 12 }
validates :events, presence: true validates :events, presence: true
validate :validate_events validate :events_validation_error, if: :invalid_events?
validate :validate_permissions validate :validate_permissions
validate :validate_template validate :validate_template
before_validation :strip_events normalizes :events, with: ->(events) { events.filter_map { |event| event.strip.presence } }
before_validation :generate_secret before_validation :generate_secret
def rotate_secret! def rotate_secret!
@ -69,8 +69,12 @@ class Webhook < ApplicationRecord
private private
def validate_events def events_validation_error
errors.add(:events, :invalid) if events.any? { |e| EVENTS.exclude?(e) } errors.add(:events, :invalid)
end
def invalid_events?
events.blank? || events.difference(EVENTS).any?
end end
def validate_permissions def validate_permissions
@ -88,10 +92,6 @@ class Webhook < ApplicationRecord
end end
end end
def strip_events
self.events = events.filter_map { |str| str.strip.presence } if events.present?
end
def generate_secret def generate_secret
self.secret = SecureRandom.hex(20) if secret.blank? self.secret = SecureRandom.hex(20) if secret.blank?
end end

View file

@ -8,6 +8,10 @@ class REST::PreviewCardSerializer < ActiveModel::Serializer
:provider_url, :html, :width, :height, :provider_url, :html, :width, :height,
:image, :image_description, :embed_url, :blurhash, :published_at :image, :image_description, :embed_url, :blurhash, :published_at
def url
object.original_url.presence || object.url
end
def image def image
object.image? ? full_asset_url(object.image.url(:original)) : nil object.image? ? full_asset_url(object.image.url(:original)) : nil
end end

View file

@ -200,7 +200,7 @@ class ActivityPub::ProcessAccountService < BaseService
end end
def check_links! def check_links!
VerifyAccountLinksWorker.perform_async(@account.id) VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), @account.id)
end end
def process_duplicate_accounts! def process_duplicate_accounts!

View file

@ -318,7 +318,7 @@ class ActivityPub::ProcessStatusUpdateService < BaseService
end end
def reset_preview_card! def reset_preview_card!
@status.preview_cards.clear @status.reset_preview_card!
LinkCrawlWorker.perform_in(rand(1..59).seconds, @status.id) LinkCrawlWorker.perform_in(rand(1..59).seconds, @status.id)
end end

View file

@ -1,12 +1,14 @@
# frozen_string_literal: true # frozen_string_literal: true
class AppSignUpService < BaseService class AppSignUpService < BaseService
include RegistrationHelper
def call(app, remote_ip, params) def call(app, remote_ip, params)
@app = app @app = app
@remote_ip = remote_ip @remote_ip = remote_ip
@params = params @params = params
raise Mastodon::NotPermittedError unless allowed_registrations? raise Mastodon::NotPermittedError unless allowed_registration?(remote_ip, invite)
ApplicationRecord.transaction do ApplicationRecord.transaction do
create_user! create_user!
@ -34,8 +36,12 @@ class AppSignUpService < BaseService
) )
end end
def invite
Invite.find_by(code: @params[:invite_code]) if @params[:invite_code].present?
end
def user_params def user_params
@params.slice(:email, :password, :agreement, :locale, :time_zone) @params.slice(:email, :password, :agreement, :locale, :time_zone, :invite_code)
end end
def account_params def account_params
@ -45,24 +51,4 @@ class AppSignUpService < BaseService
def invite_request_params def invite_request_params
{ text: @params[:reason] } { text: @params[:reason] }
end end
def allowed_registrations?
registrations_open? && !single_user_mode? && !omniauth_only? && !ip_blocked?
end
def registrations_open?
Setting.registrations_mode != 'none'
end
def single_user_mode?
Rails.configuration.x.single_user_mode
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
end
def ip_blocked?
IpBlock.where(severity: :sign_up_block).where('ip >>= ?', @remote_ip.to_s).exists?
end
end end

View file

@ -20,7 +20,7 @@ class FetchLinkCardService < BaseService
@status = status @status = status
@original_url = parse_urls @original_url = parse_urls
return if @original_url.nil? || @status.preview_cards.any? || !@status.account.link_preview? return if @original_url.nil? || @status.with_preview_card? || !@status.account.link_preview?
@url = @original_url.to_s @url = @original_url.to_s
@ -63,9 +63,9 @@ class FetchLinkCardService < BaseService
def attach_card def attach_card
with_redis_lock("attach_card:#{@status.id}") do with_redis_lock("attach_card:#{@status.id}") do
return if @status.preview_cards.any? return if @status.with_preview_card?
@status.preview_cards << @card PreviewCardsStatus.create(status: @status, preview_card: @card, url: @original_url)
Rails.cache.delete(@status) Rails.cache.delete(@status)
Trends.links.register(@status) Trends.links.register(@status)
end end

View file

@ -40,11 +40,7 @@ class UpdateAccountService < BaseService
def check_links(account) def check_links(account)
return unless account.fields.any?(&:requires_verification?) return unless account.fields.any?(&:requires_verification?)
if account.local?
VerifyAccountLinksWorker.perform_async(account.id) VerifyAccountLinksWorker.perform_async(account.id)
else
VerifyAccountLinksWorker.perform_in(rand(10.minutes.to_i), account.id)
end
end end
def process_hashtags(account) def process_hashtags(account)

View file

@ -167,7 +167,7 @@ class UpdateStatusService < BaseService
def reset_preview_card! def reset_preview_card!
return unless @status.text_previously_changed? return unless @status.text_previously_changed?
@status.preview_cards.clear @status.reset_preview_card!
LinkCrawlWorker.perform_async(@status.id) LinkCrawlWorker.perform_async(@status.id)
end end

View file

@ -1,18 +1,143 @@
--- ---
lt: lt:
activerecord:
attributes:
doorkeeper/application:
name: Programėlės pavadinimas
redirect_uri: Peradresavimo URI
scopes: Aprėptys
website: Programėlės svetainė
errors:
models:
doorkeeper/application:
attributes:
redirect_uri:
fragment_present: negali turėti fragmento.
invalid_uri: turi būti tinkamas URI.
relative_uri: turi būti absoliutus URI.
secured_uri: turi būti HTTPS/SSL URI.
doorkeeper: doorkeeper:
applications:
buttons:
authorize: Įgalinti
cancel: Atšaukti
destroy: Sunaikinti
edit: Redaguoti
submit: Pateikti
confirmations:
destroy: Ar esi įsitikinęs (-usi)?
edit:
title: Redaguoti programėlę
form:
error: Ups! Patikrink, ar formoje nėra galimų klaidų.
help:
native_redirect_uri: Naudoti %{native_redirect_uri} vietiniams bandymams
redirect_uri: Naudoti po vieną eilutę kiekvienam URI
scopes: Atskirk aprėptis tarpais. Palik tuščią, jei nori naudoti numatytąsias aprėtis.
index:
application: Programėlė
callback_url: Atgalinis URL
delete: Ištrinti
empty: Neturi jokių programėlių.
name: Pavadinimas
new: Nauja programėlė
scopes: Aprėptys
show: Rodyti
title: Tavo programėlės
new:
title: Nauja programėlė
show:
actions: Veiksmai
application_id: Kliento raktas
callback_urls: Atgalinių URL adresų
scopes: Aprėptys
secret: Kliento paslaptis
title: 'Programėlė: %{name}'
authorizations: authorizations:
buttons:
authorize: Įgalinti
deny: Atmesti
error: error:
title: Įvyko klaida. title: Įvyko klaida.
new: new:
prompt_html: "%{client_name} norėtų gauti leidimą prieigos prie tavo paskyros. Tai trečiosios šalies programėlė. <strong>Jei ja nepasitiki, neturėtum jai leisti.</strong>" prompt_html: "%{client_name} norėtų gauti leidimą prieigos prie tavo paskyros. Tai trečiosios šalies programėlė. <strong>Jei ja nepasitiki, neturėtum jai leisti.</strong>"
review_permissions: Peržiūrėti leidimus
title: Reikalingas įgaliojimas
show:
title: Nukopijuok šį įgaliojimo kodą ir įklijuok jį į programėlę.
authorized_applications: authorized_applications:
buttons:
revoke: Naikinti
confirmations:
revoke: Ar esi įsitikinęs (-usi)?
index: index:
title: Tavo leidžiamos programėlės authorized_at: Įgaliota %{date}
description_html: Tai programėlės, kurios gali pasiekti tavo paskyrą naudojant API. Jei čia yra programėlių, kurių neatpažįsti, arba jei programėlė elgiasi netinkamai, gali panaikinti jos prieigą.
last_used_at: Paskutinį kartą naudota %{date}
never_used: Niekada nenaudotas
scopes: Leidimai
superapp: Vidinis
title: Tavo įgaliotos programėlės
errors:
messages:
access_denied: Išteklių savininkas (-ė) arba įgaliojimų serveris atmetė užklausą.
credential_flow_not_configured: Išteklių savininko slaptažodžio kredencialų srautas nepavyko, nes Doorkeeper.configure.resource_owner_from_credentials nėra nesukonfigūruotas.
invalid_client: Kliento tapatybės nustatymas nepavyko dėl nežinomo kliento, neįtraukto kliento tapatybės nustatymo arba nepalaikomo tapatybės nustatymo metodo.
invalid_grant: Pateiktas įgaliojimas yra netinkamas, pasibaigęs, panaikintas, neatitinka įgaliojimo užklausoje naudoto nukreipimo URI arba buvo išduotas kitam klientui.
invalid_redirect_uri: Nukreipimo uri įtrauktas yra netinkamas.
invalid_request:
missing_param: 'Trūksta privalomo parametro: %{value}.'
request_not_authorized: Užklausą reikia įgalioti. Reikalingo parametro užklausai įgalioti trūksta arba jis netinkamas.
unknown: Užklausoje trūksta privalomo parametro, turi nepalaikomą parametro reikšmę arba yra kitaip netinkamai suformuota.
invalid_resource_owner: Pateikti išteklių savininko įgaliojimai yra netinkami arba išteklių savininko negalima surasti.
invalid_scope: Užklausos aprėptis yra netinkama, nežinoma arba netinkamai suformuota.
invalid_token:
expired: Baigėsi prieigos rakto galiojimas.
revoked: Prieigos raktas buvo panaikintas.
unknown: Prieigos raktas yra netinkamas.
resource_owner_authenticator_not_configured: Išteklių savininko suradimas nepavyko dėl to, kad Doorkeeper.configure.resource_owner_authenticator nėra sukonfigūruotas.
server_error: Įgaliojimų serveris susidūrė su netikėta sąlyga, dėl kurios negalėjo užpildyti užklausos.
temporarily_unavailable: Įgaliojimų serveris šiuo metu negali apdoroti užklausos dėl laikinos serverio perkrovos arba techninės priežiūros.
unauthorized_client: Klientas nėra įgaliotas atlikti šią užklausą šiuo metodu.
unsupported_grant_type: Įgaliojimų suteikimo tipas nepalaikomas įgaliojimų serveryje.
unsupported_response_type: Įgaliojimų serveris nepalaiko šio atsako tipo.
flash:
applications:
create:
notice: Programėlė sukurta.
destroy:
notice: Programėlė ištrinta.
update:
notice: Programėlė atnaujinta.
authorized_applications:
destroy:
notice: Programėlė panaikinta.
grouped_scopes: grouped_scopes:
access:
read: Tik skaitymo prieiga
read/write: Skaitymo ir rašymo prieiga
write: Tik rašymo prieiga
title: title:
accounts: Paskyros
admin/accounts: Paskyrų administravimas
admin/all: Visi administraciniai funkcijos
admin/reports: Ataskaitų administravimas
all: Pilna prieiga prie tavo Mastodon paskyros
blocks: Blokavimai blocks: Blokavimai
bookmarks: Žymės
conversations: Pokalbiai
crypto: Galo iki galo užšifravimas
favourites: Mėgstami
filters: Filtrai
follow: Sekimai, nutildymai ir blokavimai follow: Sekimai, nutildymai ir blokavimai
follows: Sekimai
lists: Sąrašai
media: Medijos priedai
mutes: Užtildymai
notifications: Pranešimai
push: Stumdomieji pranešimai
reports: Ataskaitos
search: Paieška
statuses: Įrašai statuses: Įrašai
layouts: layouts:
admin: admin:
@ -37,6 +162,7 @@ lt:
admin:write:domain_blocks: atlikti prižiūrėjimo veiksmus su domenų blokavimais admin:write:domain_blocks: atlikti prižiūrėjimo veiksmus su domenų blokavimais
admin:write:email_domain_blocks: atlikti prižiūrėjimo veiksmus su el. laiško domenų blokavimais admin:write:email_domain_blocks: atlikti prižiūrėjimo veiksmus su el. laiško domenų blokavimais
admin:write:ip_blocks: atlikti prižiūrėjimo veiksmus su IP blokavimais admin:write:ip_blocks: atlikti prižiūrėjimo veiksmus su IP blokavimais
admin:write:reports: atlikti paskyrų prižiūrėjimo veiksmus atsakaitams
crypto: naudoti galo iki galo šifravimą crypto: naudoti galo iki galo šifravimą
follow: modifikuoti paskyros santykius follow: modifikuoti paskyros santykius
push: gauti tavo stumiamuosius pranešimus push: gauti tavo stumiamuosius pranešimus

View file

@ -1559,6 +1559,7 @@ en:
'86400': 1 day '86400': 1 day
expires_in_prompt: Never expires_in_prompt: Never
generate: Generate invite link generate: Generate invite link
invalid: This invite is not valid
invited_by: 'You were invited by:' invited_by: 'You were invited by:'
max_uses: max_uses:
one: 1 use one: 1 use

View file

@ -131,7 +131,7 @@ fa:
reset_password: بازنشانی گذرواژه reset_password: بازنشانی گذرواژه
resubscribe: اشتراک دوباره resubscribe: اشتراک دوباره
role: نقش role: نقش
search: جستجو search: جست‌وجو
search_same_email_domain: دیگر کاربران با دامنهٔ رایانامهٔ یکسان search_same_email_domain: دیگر کاربران با دامنهٔ رایانامهٔ یکسان
search_same_ip: دیگر کاربران با IP یکسان search_same_ip: دیگر کاربران با IP یکسان
security: امنیت security: امنیت
@ -386,6 +386,10 @@ fa:
confirm_suspension: confirm_suspension:
cancel: لغو cancel: لغو
confirm: تعلیق confirm: تعلیق
permanent_action: برگرداندن تعلیق هیچ داده یا ارتباطی را برنخواهد گرداند.
preamble_html: در حال تعلیق <strong>%{domain}</strong> و همهٔ زیردامنه‌هایش هستید.
remove_all_data: این کار همهٔ داده‌های نمایه، محتوا و رسانه‌های حساب‌های این دامنه را از کارسازتان برمی‌دارد.
stop_communication: کارسازتان دیگر با این کارسازها ارتباط برقرار نخواهد کرد.
title: تأیید انسداد دامنه برای %{domain} title: تأیید انسداد دامنه برای %{domain}
created_msg: مسدودسازی دامنه در حال پردازش است created_msg: مسدودسازی دامنه در حال پردازش است
destroyed_msg: انسداد دامنه واگردانده شد destroyed_msg: انسداد دامنه واگردانده شد
@ -1219,7 +1223,7 @@ fa:
followers: این کار همهٔ پیگیران شما را از حساب فعلی به حساب تازه منتقل خواهد کرد followers: این کار همهٔ پیگیران شما را از حساب فعلی به حساب تازه منتقل خواهد کرد
only_redirect_html: شما همچنین می‌توانید حساب خود را <a href="%{path}">به یک حساب دیگر اشاره دهید</a>. only_redirect_html: شما همچنین می‌توانید حساب خود را <a href="%{path}">به یک حساب دیگر اشاره دهید</a>.
other_data: هیچ دادهٔ دیگری خودبه‌خود منتقل نخواهد شد other_data: هیچ دادهٔ دیگری خودبه‌خود منتقل نخواهد شد
redirect: نمایهٔ حساب فعلی شما به حساب تازه اشاره خواهد کرد و خودش در نتیجهٔ جستجوها ظاهر نخواهد شد redirect: نمایهٔ حساب کنونیتان به حساب تازه اشاره خواهد کرد و از جست‌وجوها حذف خواهد شد
moderation: moderation:
title: مدیریت کاربران title: مدیریت کاربران
move_handler: move_handler:

View file

@ -1 +1,5 @@
---
ig: ig:
filters:
contexts:
home: Ụlọ na ndepụta

View file

@ -1227,7 +1227,7 @@ ja:
title: セキュリティチェック title: セキュリティチェック
cloudflare_with_registering: 登録時にCloudflareの画面が表示されます。登録できないときは管理者へご連絡ください cloudflare_with_registering: 登録時にCloudflareの画面が表示されます。登録できないときは管理者へご連絡ください
confirmations: confirmations:
awaiting_review: メールアドレスは確認済みです。%{domain} のモデレーターによりアカウント登録の審査が完了すると、メールでお知らせします。 awaiting_review: メールアドレスが確認できました。%{domain} のスタッフが登録審査を行います。承認されたらメールでお知らせします!
awaiting_review_title: 登録の審査待ちです awaiting_review_title: 登録の審査待ちです
clicking_this_link: このリンクを押す clicking_this_link: このリンクを押す
login_link: ログイン login_link: ログイン

View file

@ -1689,8 +1689,8 @@ ko:
keep_polls_hint: 설문을 삭제하지 않았음 keep_polls_hint: 설문을 삭제하지 않았음
keep_self_bookmark: 북마크한 게시물 유지 keep_self_bookmark: 북마크한 게시물 유지
keep_self_bookmark_hint: 북마크한 본인의 게시물을 삭제하지 않습니다 keep_self_bookmark_hint: 북마크한 본인의 게시물을 삭제하지 않습니다
keep_self_fav: 마음에 들어한 게시물 유지 keep_self_fav: 내가 좋아요한 게시물 유지
keep_self_fav_hint: 내 스스로 마음에 들어한 본인의 게시물을 삭제하지 않습니다 keep_self_fav_hint: 스스로 좋아요를 누른 본인의 게시물을 삭제하지 않습니다
min_age: min_age:
'1209600': 2 '1209600': 2
'15778476': 6 개월 '15778476': 6 개월

View file

@ -1041,6 +1041,14 @@ nn:
hint_html: Berre ein ting til! Vi må bekrefte at du er et menneske (så vi kan halde spam ute!). Løys CAPTCHA-en nedanfor og klikk "Fortsett". hint_html: Berre ein ting til! Vi må bekrefte at du er et menneske (så vi kan halde spam ute!). Løys CAPTCHA-en nedanfor og klikk "Fortsett".
title: Sikkerheitssjekk title: Sikkerheitssjekk
confirmations: confirmations:
awaiting_review: Din e-post adresse er bekreftet! %{domain} ansatte gjennomgår nå registreringen din. Du vil motta en e-post hvis de godkjenner din konto!
awaiting_review_title: Din registrering blir vurdert
clicking_this_link: klikke på denne lenken
login_link: logg inn
proceed_to_login_html: Du kan nå fortsette til %{login_link}.
redirect_to_app_html: Du burde bli omdirigert til <strong>%{app_name}</strong> -appen. Hvis det ikke skjedde, kan du prøve %{clicking_this_link} eller manuelt gå tilbake til appen.
registration_complete: Registreringen på %{domain} er nå fullført!
welcome_title: Velkommen, %{name}!
wrong_email_hint: Viss epostadressa er feil, kan du endra ho i kontoinnstillingane. wrong_email_hint: Viss epostadressa er feil, kan du endra ho i kontoinnstillingane.
delete_account: Slett konto delete_account: Slett konto
delete_account_html: Om du vil sletta kontoen din, kan du <a href="%{path}">gå hit</a>. Du vert spurd etter stadfesting. delete_account_html: Om du vil sletta kontoen din, kan du <a href="%{path}">gå hit</a>. Du vert spurd etter stadfesting.

View file

@ -772,6 +772,11 @@
approved: Godkjenning kreves for påmelding approved: Godkjenning kreves for påmelding
none: Ingen kan melde seg inn none: Ingen kan melde seg inn
open: Hvem som helst kan melde seg inn open: Hvem som helst kan melde seg inn
security:
authorized_fetch: Krev autentisering fra fødererte servere
authorized_fetch_hint: Krav om godkjenning fra fødererte servere muliggjør strengere håndhevelse av blokker på både brukernivå og servernivå. Dette går imidlertid på bekostning av en ytelsesstraff, reduserer rekkevidden til svarene dine og kan introdusere kompatibilitetsproblemer med enkelte fødererte tjenester. I tillegg vil dette ikke hindre dedikerte aktører i å hente dine offentlige innlegg og kontoer.
authorized_fetch_overridden_hint: Du kan for øyeblikket ikke endre denne innstillingen fordi den overstyres av en miljøvariabel.
federation_authentication: Håndheving av føderasjonsautentisering
title: Serverinnstillinger title: Serverinnstillinger
site_uploads: site_uploads:
delete: Slett den opplastede filen delete: Slett den opplastede filen
@ -1036,6 +1041,14 @@
hint_html: Bare en ting til! Vi må bekrefte at du er et menneske (dette er slik at vi kan holde spam ute!). Løs CAPTCHA nedenfor og klikk "Fortsett". hint_html: Bare en ting til! Vi må bekrefte at du er et menneske (dette er slik at vi kan holde spam ute!). Løs CAPTCHA nedenfor og klikk "Fortsett".
title: Sikkerhetskontroll title: Sikkerhetskontroll
confirmations: confirmations:
awaiting_review: Din e-post adresse er bekreftet! %{domain} ansatte gjennomgår nå registreringen din. Du vil motta en e-post hvis de godkjenner din konto!
awaiting_review_title: Din registrering blir vurdert
clicking_this_link: klikke på denne lenken
login_link: logg inn
proceed_to_login_html: Du kan nå fortsette til %{login_link}.
redirect_to_app_html: Du burde bli omdirigert til <strong>%{app_name}</strong> -appen. Hvis det ikke skjedde, kan du prøve %{clicking_this_link} eller manuelt gå tilbake til appen.
registration_complete: Registreringen på %{domain} er nå fullført!
welcome_title: Velkommen, %{name}!
wrong_email_hint: Hvis e-postadressen ikke er riktig, kan du endre den i kontoinnstillingene. wrong_email_hint: Hvis e-postadressen ikke er riktig, kan du endre den i kontoinnstillingene.
delete_account: Slett konto delete_account: Slett konto
delete_account_html: Hvis du ønsker å slette kontoen din, kan du <a href="%{path}">gå hit</a>. Du vil bli spurt om bekreftelse. delete_account_html: Hvis du ønsker å slette kontoen din, kan du <a href="%{path}">gå hit</a>. Du vil bli spurt om bekreftelse.
@ -1739,6 +1752,10 @@
month: "%b %Y" month: "%b %Y"
time: "%H:%M" time: "%H:%M"
with_time_zone: "%-d. %b %Y, %H:%M %Z" with_time_zone: "%-d. %b %Y, %H:%M %Z"
translation:
errors:
quota_exceeded: Den serveromfattende brukskvoten for oversettelsestjenesten er overskredet.
too_many_requests: Det har nylig vært for mange forespørsler til oversettelsestjenesten.
two_factor_authentication: two_factor_authentication:
add: Legg til add: Legg til
disable: Skru av disable: Skru av

View file

@ -5,7 +5,7 @@ he:
account: account:
discoverable: הפוסטים והפרופיל שלך עשויים להיות מוצגים או מומלצים באזורים שונים באתר וייתכן שהפרופיל שלך יוצע למשתמשים אחרים. discoverable: הפוסטים והפרופיל שלך עשויים להיות מוצגים או מומלצים באזורים שונים באתר וייתכן שהפרופיל שלך יוצע למשתמשים אחרים.
display_name: שמך המלא או שם הכיף שלך. display_name: שמך המלא או שם הכיף שלך.
fields: עמוד הבית שלך, כינויי גוף, גיל, וכל מידע אחר לפי העדפתך האישית. fields: עמוד הבית שלך, לשון הפנייה, גיל, וכל מידע אחר לפי העדפתך האישית.
indexable: ההודעות הפומביות שלך עשויות להופיע בתוצאות חיפוש במסטודון. אחרים שהדהדו, חיבבו או ענו להודעות האלו יוכלו למצוא אותן בחיפוש בכל מקרה. indexable: ההודעות הפומביות שלך עשויות להופיע בתוצאות חיפוש במסטודון. אחרים שהדהדו, חיבבו או ענו להודעות האלו יוכלו למצוא אותן בחיפוש בכל מקרה.
note: 'ניתן לאזכר @אחרים או #תגיות.' note: 'ניתן לאזכר @אחרים או #תגיות.'
show_collections: אנשים יוכלו לדפדף בין העוקבים והנעקבים שלך. אנשים שאת.ה עוקב.ת אחריהם יראו את המעקב אחריהם כרגיל. show_collections: אנשים יוכלו לדפדף בין העוקבים והנעקבים שלך. אנשים שאת.ה עוקב.ת אחריהם יראו את המעקב אחריהם כרגיל.

View file

@ -323,6 +323,7 @@
url: Endepunkt lenke url: Endepunkt lenke
'no': Nei 'no': Nei
not_recommended: Ikke anbefalt not_recommended: Ikke anbefalt
overridden: Overstyrt
recommended: Anbefalt recommended: Anbefalt
required: required:
mark: "*" mark: "*"

View file

@ -88,6 +88,8 @@ Rails.application.routes.draw do
resource :outbox, only: [:show], module: :activitypub resource :outbox, only: [:show], module: :activitypub
end end
get '/invite/:invite_code', constraints: ->(req) { req.format == :json }, to: 'api/v1/invites#show'
devise_scope :user do devise_scope :user do
get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite get '/invite/:invite_code', to: 'auth/registrations#new', as: :public_invite

View file

@ -1,6 +1,6 @@
module.exports = { module.exports = {
test: /\.svg$/, test: /\.svg$/,
include: /node_modules\/@material-symbols/, include: [/node_modules\/@material-symbols/, /svg-icons/],
issuer: /\.[jt]sx?$/, issuer: /\.[jt]sx?$/,
use: [ use: [
{ {

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddURLToPreviewCardsStatuses < ActiveRecord::Migration[7.0]
def change
add_column :preview_cards_statuses, :url, :string
end
end

View file

@ -50,15 +50,6 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true t.index ["account_id", "domain"], name: "index_account_domain_blocks_on_account_id_and_domain", unique: true
end end
create_table "account_groups", force: :cascade do |t|
t.bigint "account_id", null: false
t.bigint "group_account_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["account_id"], name: "index_account_groups_on_account_id"
t.index ["group_account_id"], name: "index_account_groups_on_group_account_id"
end
create_table "account_migrations", force: :cascade do |t| create_table "account_migrations", force: :cascade do |t|
t.bigint "account_id" t.bigint "account_id"
t.string "acct", default: "", null: false t.string "acct", default: "", null: false
@ -165,11 +156,11 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.string "url" t.string "url"
t.string "avatar_file_name" t.string "avatar_file_name"
t.string "avatar_content_type" t.string "avatar_content_type"
t.integer "avatar_file_size" t.bigint "avatar_file_size"
t.datetime "avatar_updated_at", precision: nil t.datetime "avatar_updated_at", precision: nil
t.string "header_file_name" t.string "header_file_name"
t.string "header_content_type" t.string "header_content_type"
t.integer "header_file_size" t.bigint "header_file_size"
t.datetime "header_updated_at", precision: nil t.datetime "header_updated_at", precision: nil
t.string "avatar_remote_url" t.string "avatar_remote_url"
t.boolean "locked", default: false, null: false t.boolean "locked", default: false, null: false
@ -193,8 +184,8 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.integer "avatar_storage_schema_version" t.integer "avatar_storage_schema_version"
t.integer "header_storage_schema_version" t.integer "header_storage_schema_version"
t.string "devices_url" t.string "devices_url"
t.integer "suspension_origin"
t.datetime "sensitized_at", precision: nil t.datetime "sensitized_at", precision: nil
t.integer "suspension_origin"
t.boolean "trendable" t.boolean "trendable"
t.datetime "reviewed_at", precision: nil t.datetime "reviewed_at", precision: nil
t.datetime "requested_review_at", precision: nil t.datetime "requested_review_at", precision: nil
@ -332,6 +323,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.index ["ignore_reblog"], name: "index_antennas_on_ignore_reblog" t.index ["ignore_reblog"], name: "index_antennas_on_ignore_reblog"
t.index ["list_id"], name: "index_antennas_on_list_id" t.index ["list_id"], name: "index_antennas_on_list_id"
t.index ["stl"], name: "index_antennas_on_stl" t.index ["stl"], name: "index_antennas_on_stl"
t.index ["with_media_only"], name: "index_antennas_on_with_media_only"
end end
create_table "appeals", force: :cascade do |t| create_table "appeals", force: :cascade do |t|
@ -490,7 +482,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.string "domain" t.string "domain"
t.string "image_file_name" t.string "image_file_name"
t.string "image_content_type" t.string "image_content_type"
t.integer "image_file_size" t.bigint "image_file_size"
t.datetime "image_updated_at", precision: nil t.datetime "image_updated_at", precision: nil
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
@ -710,7 +702,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.string "data_file_name" t.string "data_file_name"
t.string "data_content_type" t.string "data_content_type"
t.integer "data_file_size" t.bigint "data_file_size"
t.datetime "data_updated_at", precision: nil t.datetime "data_updated_at", precision: nil
t.bigint "account_id", null: false t.bigint "account_id", null: false
t.boolean "overwrite", default: false, null: false t.boolean "overwrite", default: false, null: false
@ -741,12 +733,12 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
end end
create_table "ip_blocks", force: :cascade do |t| create_table "ip_blocks", force: :cascade do |t|
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.datetime "expires_at", precision: nil
t.inet "ip", default: "0.0.0.0", null: false t.inet "ip", default: "0.0.0.0", null: false
t.integer "severity", default: 0, null: false t.integer "severity", default: 0, null: false
t.datetime "expires_at", precision: nil
t.text "comment", default: "", null: false t.text "comment", default: "", null: false
t.datetime "created_at", precision: nil, null: false
t.datetime "updated_at", precision: nil, null: false
t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true
end end
@ -808,7 +800,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.bigint "status_id" t.bigint "status_id"
t.string "file_file_name" t.string "file_file_name"
t.string "file_content_type" t.string "file_content_type"
t.integer "file_file_size" t.bigint "file_file_size"
t.datetime "file_updated_at", precision: nil t.datetime "file_updated_at", precision: nil
t.string "remote_url", default: "", null: false t.string "remote_url", default: "", null: false
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
@ -824,8 +816,8 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.integer "file_storage_schema_version" t.integer "file_storage_schema_version"
t.string "thumbnail_file_name" t.string "thumbnail_file_name"
t.string "thumbnail_content_type" t.string "thumbnail_content_type"
t.integer "thumbnail_file_size" t.bigint "thumbnail_file_size"
t.datetime "thumbnail_updated_at", precision: nil t.datetime "thumbnail_updated_at"
t.string "thumbnail_remote_url" t.string "thumbnail_remote_url"
t.index ["account_id", "status_id"], name: "index_media_attachments_on_account_id_and_status_id", order: { status_id: :desc } t.index ["account_id", "status_id"], name: "index_media_attachments_on_account_id_and_status_id", order: { status_id: :desc }
t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id", where: "(scheduled_status_id IS NOT NULL)" t.index ["scheduled_status_id"], name: "index_media_attachments_on_scheduled_status_id", where: "(scheduled_status_id IS NOT NULL)"
@ -992,7 +984,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.string "description", default: "", null: false t.string "description", default: "", null: false
t.string "image_file_name" t.string "image_file_name"
t.string "image_content_type" t.string "image_content_type"
t.integer "image_file_size" t.bigint "image_file_size"
t.datetime "image_updated_at", precision: nil t.datetime "image_updated_at", precision: nil
t.integer "type", default: 0, null: false t.integer "type", default: 0, null: false
t.text "html", default: "", null: false t.text "html", default: "", null: false
@ -1020,6 +1012,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
create_table "preview_cards_statuses", primary_key: ["status_id", "preview_card_id"], force: :cascade do |t| create_table "preview_cards_statuses", primary_key: ["status_id", "preview_card_id"], force: :cascade do |t|
t.bigint "preview_card_id", null: false t.bigint "preview_card_id", null: false
t.bigint "status_id", null: false t.bigint "status_id", null: false
t.string "url"
end end
create_table "relays", force: :cascade do |t| create_table "relays", force: :cascade do |t|
@ -1115,7 +1108,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.string "var", default: "", null: false t.string "var", default: "", null: false
t.string "file_file_name" t.string "file_file_name"
t.string "file_content_type" t.string "file_content_type"
t.integer "file_file_size" t.bigint "file_file_size"
t.datetime "file_updated_at", precision: nil t.datetime "file_updated_at", precision: nil
t.json "meta" t.json "meta"
t.datetime "created_at", precision: nil, null: false t.datetime "created_at", precision: nil, null: false
@ -1161,8 +1154,8 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
create_table "status_pins", force: :cascade do |t| create_table "status_pins", force: :cascade do |t|
t.bigint "account_id", null: false t.bigint "account_id", null: false
t.bigint "status_id", null: false t.bigint "status_id", null: false
t.datetime "created_at", precision: nil, default: -> { "now()" }, null: false t.datetime "created_at", precision: nil, default: -> { "CURRENT_TIMESTAMP" }, null: false
t.datetime "updated_at", precision: nil, default: -> { "now()" }, null: false t.datetime "updated_at", precision: nil, default: -> { "CURRENT_TIMESTAMP" }, null: false
t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true t.index ["account_id", "status_id"], name: "index_status_pins_on_account_id_and_status_id", unique: true
t.index ["status_id"], name: "index_status_pins_on_status_id" t.index ["status_id"], name: "index_status_pins_on_status_id"
end end
@ -1187,7 +1180,6 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.datetime "updated_at", precision: nil, null: false t.datetime "updated_at", precision: nil, null: false
t.string "emoji_reactions" t.string "emoji_reactions"
t.integer "emoji_reactions_count", default: 0, null: false t.integer "emoji_reactions_count", default: 0, null: false
t.integer "test", default: 0, null: false
t.integer "emoji_reaction_accounts_count", default: 0, null: false t.integer "emoji_reaction_accounts_count", default: 0, null: false
t.integer "status_referred_by_count", default: 0, null: false t.integer "status_referred_by_count", default: 0, null: false
t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true t.index ["status_id"], name: "index_status_stats_on_status_id", unique: true
@ -1412,7 +1404,6 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
add_foreign_key "account_conversations", "conversations", on_delete: :cascade add_foreign_key "account_conversations", "conversations", on_delete: :cascade
add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade add_foreign_key "account_deletion_requests", "accounts", on_delete: :cascade
add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade add_foreign_key "account_domain_blocks", "accounts", name: "fk_206c6029bd", on_delete: :cascade
add_foreign_key "account_groups", "accounts", on_delete: :cascade
add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify add_foreign_key "account_migrations", "accounts", column: "target_account_id", on_delete: :nullify
add_foreign_key "account_migrations", "accounts", on_delete: :cascade add_foreign_key "account_migrations", "accounts", on_delete: :cascade
add_foreign_key "account_moderation_notes", "accounts" add_foreign_key "account_moderation_notes", "accounts"

View file

@ -19,7 +19,7 @@ const config = {
// Those packages are ESM, so we need them to be processed by Babel // Those packages are ESM, so we need them to be processed by Babel
transformIgnorePatterns: ['/node_modules/(?!(redent|strip-indent)/)'], transformIgnorePatterns: ['/node_modules/(?!(redent|strip-indent)/)'],
coverageDirectory: '<rootDir>/coverage', coverageDirectory: '<rootDir>/coverage',
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/app/javascript'], moduleDirectories: ['node_modules', '<rootDir>/app/javascript'],
moduleNameMapper: { moduleNameMapper: {
'\\.svg$': '<rootDir>/app/javascript/__mocks__/svg.js', '\\.svg$': '<rootDir>/app/javascript/__mocks__/svg.js',
}, },

View file

@ -69,7 +69,7 @@ namespace :tests do
exit(1) exit(1)
end end
unless Status.find(12).preview_cards.pluck(:url) == ['https://joinmastodon.org/'] unless PreviewCard.where(id: PreviewCardsStatus.where(status_id: 12).select(:preview_card_id)).pluck(:url) == ['https://joinmastodon.org/']
puts 'Preview cards not deduplicated as expected' puts 'Preview cards not deduplicated as expected'
exit(1) exit(1)
end end

View file

@ -181,7 +181,7 @@
"@types/react-dom": "^18.2.4", "@types/react-dom": "^18.2.4",
"@types/react-helmet": "^6.1.6", "@types/react-helmet": "^6.1.6",
"@types/react-immutable-proptypes": "^2.1.0", "@types/react-immutable-proptypes": "^2.1.0",
"@types/react-motion": "^0.0.36", "@types/react-motion": "^0.0.37",
"@types/react-overlays": "^3.1.0", "@types/react-overlays": "^3.1.0",
"@types/react-router": "^5.1.20", "@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
@ -205,7 +205,7 @@
"eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-formatjs": "^4.10.1",
"eslint-plugin-import": "~2.29.0", "eslint-plugin-import": "~2.29.0",
"eslint-plugin-jsdoc": "^46.1.0", "eslint-plugin-jsdoc": "^46.1.0",
"eslint-plugin-jsx-a11y": "~6.7.1", "eslint-plugin-jsx-a11y": "~6.8.0",
"eslint-plugin-prettier": "^5.0.0", "eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "~6.1.1", "eslint-plugin-promise": "~6.1.1",
"eslint-plugin-react": "~7.33.0", "eslint-plugin-react": "~7.33.0",

View file

@ -20,8 +20,7 @@ RSpec.describe Admin::AccountsController do
it 'filters with parameters' do it 'filters with parameters' do
account_filter = instance_double(AccountFilter, results: Account.all) account_filter = instance_double(AccountFilter, results: Account.all)
allow(AccountFilter).to receive(:new).and_return(account_filter) allow(AccountFilter).to receive(:new).and_return(account_filter)
params = {
get :index, params: {
origin: 'local', origin: 'local',
by_domain: 'domain', by_domain: 'domain',
status: 'active', status: 'active',
@ -31,17 +30,9 @@ RSpec.describe Admin::AccountsController do
ip: '0.0.0.42', ip: '0.0.0.42',
} }
expect(AccountFilter).to have_received(:new) do |params| get :index, params: params
h = params.to_h
expect(h[:origin]).to eq 'local' expect(AccountFilter).to have_received(:new).with(hash_including(params))
expect(h[:by_domain]).to eq 'domain'
expect(h[:status]).to eq 'active'
expect(h[:username]).to eq 'username'
expect(h[:display_name]).to eq 'display name'
expect(h[:email]).to eq 'local-part@domain'
expect(h[:ip]).to eq '0.0.0.42'
end
end end
it 'paginates accounts' do it 'paginates accounts' do

View file

@ -2,14 +2,44 @@
require 'rails_helper' require 'rails_helper'
describe Api::V1::Trends::LinksController do RSpec.describe Api::V1::Trends::LinksController do
render_views render_views
describe 'GET #index' do describe 'GET #index' do
around do |example|
previous = Setting.trends
example.run
Setting.trends = previous
end
context 'when trends are disabled' do
before { Setting.trends = false }
it 'returns http success' do it 'returns http success' do
get :index get :index
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end
context 'when trends are enabled' do
before { Setting.trends = true }
it 'returns http success' do
prepare_trends
stub_const('Api::V1::Trends::LinksController::DEFAULT_LINKS_LIMIT', 2)
get :index
expect(response).to have_http_status(200)
expect(response.headers).to include('Link')
end
def prepare_trends
Fabricate.times(3, :preview_card, trendable: true, language: 'en').each do |link|
2.times { |i| Trends.links.add(link, i) }
end
Trends::Links.new(threshold: 1).refresh
end
end
end
end end

View file

@ -2,14 +2,44 @@
require 'rails_helper' require 'rails_helper'
describe Api::V1::Trends::StatusesController do RSpec.describe Api::V1::Trends::StatusesController do
render_views render_views
describe 'GET #index' do describe 'GET #index' do
around do |example|
previous = Setting.trends
example.run
Setting.trends = previous
end
context 'when trends are disabled' do
before { Setting.trends = false }
it 'returns http success' do it 'returns http success' do
get :index get :index
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end
context 'when trends are enabled' do
before { Setting.trends = true }
it 'returns http success' do
prepare_trends
stub_const('Api::BaseController::DEFAULT_STATUSES_LIMIT', 2)
get :index
expect(response).to have_http_status(200)
expect(response.headers).to include('Link')
end
def prepare_trends
Fabricate.times(3, :status, trendable: true, language: 'en').each do |status|
2.times { |i| Trends.statuses.add(status, i) }
end
Trends::Statuses.new(threshold: 1, decay_threshold: -1).refresh
end
end
end
end end

View file

@ -6,16 +6,41 @@ RSpec.describe Api::V1::Trends::TagsController do
render_views render_views
describe 'GET #index' do describe 'GET #index' do
before do around do |example|
Fabricate.times(10, :tag).each do |tag| previous = Setting.trends
10.times { |i| Trends.tags.add(tag, i) } example.run
Setting.trends = previous
end end
get :index context 'when trends are disabled' do
end before { Setting.trends = false }
it 'returns http success' do it 'returns http success' do
get :index
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
expect(response.headers).to_not include('Link')
end
end
context 'when trends are enabled' do
before { Setting.trends = true }
it 'returns http success' do
prepare_trends
stub_const('Api::V1::Trends::TagsController::DEFAULT_TAGS_LIMIT', 2)
get :index
expect(response).to have_http_status(200)
expect(response.headers).to include('Link')
end
def prepare_trends
Fabricate.times(3, :tag, trendable: true).each do |tag|
2.times { |i| Trends.tags.add(tag, i) }
end
Trends::Tags.new(threshold: 1).refresh
end
end end
end end
end end

View file

@ -49,10 +49,12 @@ describe MediaComponentHelper do
end end
describe 'render_card_component' do describe 'render_card_component' do
let(:status) { Fabricate(:status, preview_cards: [Fabricate(:preview_card)]) } let(:status) { Fabricate(:status) }
let(:result) { helper.render_card_component(status) } let(:result) { helper.render_card_component(status) }
before do before do
PreviewCardsStatus.create(status: status, preview_card: Fabricate(:preview_card))
without_partial_double_verification do without_partial_double_verification do
allow(helper).to receive(:current_account).and_return(status.account) allow(helper).to receive(:current_account).and_return(status.account)
end end

View file

@ -5,6 +5,37 @@ require 'rails_helper'
RSpec.describe Webhook do RSpec.describe Webhook do
let(:webhook) { Fabricate(:webhook) } let(:webhook) { Fabricate(:webhook) }
describe 'Validations' do
it 'requires presence of events' do
record = described_class.new(events: nil)
record.valid?
expect(record).to model_have_error_on_field(:events)
end
it 'requires non-empty events value' do
record = described_class.new(events: [])
record.valid?
expect(record).to model_have_error_on_field(:events)
end
it 'requires valid events value from EVENTS' do
record = described_class.new(events: ['account.invalid'])
record.valid?
expect(record).to model_have_error_on_field(:events)
end
end
describe 'Normalizations' do
it 'cleans up events values' do
record = described_class.new(events: ['account.approved', 'account.created ', ''])
expect(record.events).to eq(%w(account.approved account.created))
end
end
describe '#rotate_secret!' do describe '#rotate_secret!' do
it 'changes the secret' do it 'changes the secret' do
previous_value = webhook.secret previous_value = webhook.secret

View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
require 'rails_helper'
describe 'invites' do
let(:invite) { Fabricate(:invite) }
context 'when requesting a JSON document' do
it 'returns a JSON document with expected attributes' do
get "/invite/#{invite.code}", headers: { 'Accept' => 'application/activity+json' }
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'application/json'
expect(body_as_json[:invite_code]).to eq invite.code
end
end
context 'when not requesting a JSON document' do
it 'returns an HTML page' do
get "/invite/#{invite.code}"
expect(response).to have_http_status(200)
expect(response.media_type).to eq 'text/html'
end
end
end

View file

@ -10,40 +10,65 @@ RSpec.describe AppSignUpService, type: :service do
let(:remote_ip) { IPAddr.new('198.0.2.1') } let(:remote_ip) { IPAddr.new('198.0.2.1') }
describe '#call' do describe '#call' do
it 'returns nil when registrations are closed' do let(:params) { good_params }
shared_examples 'successful registration' do
it 'creates an unconfirmed user with access token and the app\'s scope', :aggregate_failures do
access_token = subject.call(app, remote_ip, params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
expect(user.confirmed?).to be false
expect(user.account).to_not be_nil
expect(user.invite_request).to be_nil
end
end
context 'when registrations are closed' do
around do |example|
tmp = Setting.registrations_mode tmp = Setting.registrations_mode
Setting.registrations_mode = 'none' Setting.registrations_mode = 'none'
expect { subject.call(app, remote_ip, good_params) }.to raise_error Mastodon::NotPermittedError
example.run
Setting.registrations_mode = tmp Setting.registrations_mode = tmp
end end
it 'raises an error', :aggregate_failures do
expect { subject.call(app, remote_ip, good_params) }.to raise_error Mastodon::NotPermittedError
end
context 'when using a valid invite' do
let(:params) { good_params.merge({ invite_code: invite.code }) }
let(:invite) { Fabricate(:invite) }
before do
invite.user.approve!
end
it_behaves_like 'successful registration'
end
context 'when using an invalid invite' do
let(:params) { good_params.merge({ invite_code: invite.code }) }
let(:invite) { Fabricate(:invite, uses: 1, max_uses: 1) }
it 'raises an error', :aggregate_failures do
expect { subject.call(app, remote_ip, params) }.to raise_error Mastodon::NotPermittedError
end
end
end
it 'raises an error when params are missing' do it 'raises an error when params are missing' do
expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid
end end
it 'creates an unconfirmed user with access token' do it_behaves_like 'successful registration'
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
expect(user.confirmed?).to be false
end
it 'creates access token with the app\'s scopes' do
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
expect(access_token.scopes.to_s).to eq 'read write'
end
it 'creates an account' do
access_token = subject.call(app, remote_ip, good_params)
expect(access_token).to_not be_nil
user = User.find_by(id: access_token.resource_owner_id)
expect(user).to_not be_nil
expect(user.account).to_not be_nil
expect(user.invite_request).to be_nil
end
context 'when given an invite request text' do
it 'creates an account with invite request text' do it 'creates an account with invite request text' do
access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar')) access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar'))
expect(access_token).to_not be_nil expect(access_token).to_not be_nil
@ -53,3 +78,4 @@ RSpec.describe AppSignUpService, type: :service do
end end
end end
end end
end

View file

@ -121,7 +121,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis') } let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis') }
it 'decodes the HTML' do it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('SJISのページ') expect(status.preview_card.title).to eq('SJISのページ')
end end
end end
@ -129,7 +129,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis_with_wrong_charset') } let(:status) { Fabricate(:status, text: 'Check out http://example.com/sjis_with_wrong_charset') }
it 'decodes the HTML despite the wrong charset header' do it 'decodes the HTML despite the wrong charset header' do
expect(status.preview_cards.first.title).to eq('SJISのページ') expect(status.preview_card.title).to eq('SJISのページ')
end end
end end
@ -137,7 +137,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') } let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') }
it 'decodes the HTML' do it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.') expect(status.preview_card.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
end end
end end
@ -145,7 +145,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') } let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') }
it 'decodes the HTML' do it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('сэмпл текст') expect(status.preview_card.title).to eq('сэмпл текст')
end end
end end
@ -253,11 +253,21 @@ RSpec.describe FetchLinkCardService, type: :service do
expect(status.preview_card.title).to eq 'Hello world' expect(status.preview_card.title).to eq 'Hello world'
end end
end end
context 'with URL but author is not allow preview card' do
let(:account) { Fabricate(:user, settings: { link_preview: false }).account }
let(:status) { Fabricate(:status, text: 'http://example.com/html', account: account) }
it 'not create preview card' do
expect(status.preview_card).to be_nil
end
end
end end
context 'with a remote status' do context 'with a remote status' do
let(:account) { Fabricate(:account, domain: 'example.com') }
let(:status) do let(:status) do
Fabricate(:status, account: Fabricate(:account, domain: 'example.com'), text: <<-TEXT) Fabricate(:status, account: account, text: <<-TEXT)
Habt ihr ein paar gute Links zu <a>foo</a> Habt ihr ein paar gute Links zu <a>foo</a>
#<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen? #<span class="tag"><a href="https://quitter.se/tag/wannacry" target="_blank" rel="tag noopener noreferrer" title="https://quitter.se/tag/wannacry">Wannacry</a></span> herumfliegen?
Ich will mal unter <br> <a href="http://example.com/not-found" target="_blank" rel="noopener noreferrer" title="http://example.com/not-found">http://example.com/not-found</a> was sammeln. ! Ich will mal unter <br> <a href="http://example.com/not-found" target="_blank" rel="noopener noreferrer" title="http://example.com/not-found">http://example.com/not-found</a> was sammeln. !
@ -272,6 +282,14 @@ RSpec.describe FetchLinkCardService, type: :service do
it 'ignores URLs to hashtags' do it 'ignores URLs to hashtags' do
expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made
end end
context 'with URL but author is not allow preview card' do
let(:account) { Fabricate(:account, domain: 'example.com', settings: { link_preview: false }) }
it 'not create link preview' do
expect(status.preview_card).to be_nil
end
end
end end
context 'with a remote status of reference' do context 'with a remote status of reference' do

View file

@ -23,11 +23,11 @@ RSpec.describe UpdateStatusService, type: :service do
end end
context 'when text changes' do context 'when text changes' do
let!(:status) { Fabricate(:status, text: 'Foo') } let(:status) { Fabricate(:status, text: 'Foo') }
let(:preview_card) { Fabricate(:preview_card) } let(:preview_card) { Fabricate(:preview_card) }
before do before do
status.preview_cards << preview_card PreviewCardsStatus.create(status: status, preview_card: preview_card)
subject.call(status, status.account_id, text: 'Bar') subject.call(status, status.account_id, text: 'Bar')
end end
@ -45,11 +45,11 @@ RSpec.describe UpdateStatusService, type: :service do
end end
context 'when content warning changes' do context 'when content warning changes' do
let!(:status) { Fabricate(:status, text: 'Foo', spoiler_text: '') } let(:status) { Fabricate(:status, text: 'Foo', spoiler_text: '') }
let(:preview_card) { Fabricate(:preview_card) } let(:preview_card) { Fabricate(:preview_card) }
before do before do
status.preview_cards << preview_card PreviewCardsStatus.create(status: status, preview_card: preview_card)
subject.call(status, status.account_id, text: 'Foo', spoiler_text: 'Bar') subject.call(status, status.account_id, text: 'Foo', spoiler_text: 'Bar')
end end

1491
yarn.lock

File diff suppressed because it is too large Load diff