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

View file

@ -144,7 +144,7 @@ class PublicStatusesIndex < Chewy::Index
index_scope ::Status.unscoped
.kept
.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
field(:id, type: 'long')

View file

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

View file

@ -1,6 +1,8 @@
# frozen_string_literal: true
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 -> { doorkeeper_authorize! :follow, :write, :'write:follows' }, only: [:follow, :unfollow, :remove_from_followers]
before_action -> { doorkeeper_authorize! :follow, :write, :'write:mutes' }, only: [:mute, :unmute]
@ -90,18 +92,14 @@ class Api::V1::AccountsController < Api::BaseController
end
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
def check_enabled_registrations
forbidden if single_user_mode? || omniauth_only? || !allowed_registrations?
end
def allowed_registrations?
Setting.registrations_mode != 'none'
end
def omniauth_only?
ENV['OMNIAUTH_ONLY'] == 'true'
forbidden unless allowed_registration?(request.remote_ip, invite)
end
end

View file

@ -41,10 +41,10 @@ class Api::V1::ConversationsController < Api::BaseController
account: :account_stat,
last_status: [
:media_attachments,
:preview_cards,
:status_stat,
:tags,
{
preview_cards_status: :preview_card,
active_mentions: [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
class Api::V1::Statuses::BookmarksController < Api::BaseController
include Authorization
class Api::V1::Statuses::BookmarksController < Api::V1::Statuses::BaseController
before_action -> { doorkeeper_authorize! :write, :'write:bookmarks' }
before_action :require_user!
before_action :set_status, only: [:create]
skip_before_action :set_status, only: [:destroy]
def create
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
not_found
end
private
def set_status
@status = Status.find(params[:status_id])
authorize @status, :show?
rescue Mastodon::NotPermittedError
not_found
end
end

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
# frozen_string_literal: true
class Auth::RegistrationsController < Devise::RegistrationsController
include RegistrationHelper
include RegistrationSpamConcern
layout :determine_layout
@ -82,19 +83,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def check_enabled_registrations
redirect_to root_path if single_user_mode? || omniauth_only? || !allowed_registrations? || ip_blocked?
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?
redirect_to root_path unless allowed_registration?(request.remote_ip, @invite)
end
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 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 { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -426,6 +428,7 @@ class StatusActionBar extends ImmutablePureComponent {
let replyIcon;
let replyIconComponent;
let replyTitle;
if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply';
replyIconComponent = ReplyIcon;
@ -438,15 +441,20 @@ class StatusActionBar extends ImmutablePureComponent {
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
let reblogTitle = '';
let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
}
const filterButton = this.props.onFilter && (
@ -472,7 +480,7 @@ class StatusActionBar extends ImmutablePureComponent {
return (
<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={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 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}

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 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 { WithRouterPropTypes } from 'mastodon/utils/react_router';
@ -337,6 +339,7 @@ class ActionBar extends PureComponent {
let replyIcon;
let replyIconComponent;
if (status.get('in_reply_to_id', null) === null) {
replyIcon = 'reply';
replyIconComponent = ReplyIcon;
@ -347,15 +350,20 @@ class ActionBar extends PureComponent {
const reblogPrivate = status.getIn(['account', 'id']) === me && status.get('visibility_ex') === 'private';
let reblogTitle;
let reblogTitle, reblogIconComponent;
if (status.get('reblogged')) {
reblogTitle = intl.formatMessage(messages.cancel_reblog_private);
reblogIconComponent = publicStatus ? RepeatIcon : RepeatPrivateIcon;
} else if (publicStatus) {
reblogTitle = intl.formatMessage(messages.reblog);
reblogIconComponent = RepeatIcon;
} else if (reblogPrivate) {
reblogTitle = intl.formatMessage(messages.reblog_private);
reblogIconComponent = RepeatPrivateIcon;
} else {
reblogTitle = intl.formatMessage(messages.cannot_reblog);
reblogIconComponent = RepeatDisabledIcon;
}
const emojiReactionAvailableServer = !isHideItem('emoji_reaction_unavailable_server') || status.get('emoji_reaction_available_server');
@ -374,8 +382,8 @@ class ActionBar extends PureComponent {
return (
<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 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 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={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='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}

View file

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

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Otsitulemused",
"emoji_button.symbols": "Sümbolid",
"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_timeline": "Siin postitusi ei ole!",
"empty_column.account_unavailable": "Profiil pole saadaval",

View file

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

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Hakutulokset",
"emoji_button.symbols": "Symbolit",
"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_timeline": "Ei viestejä täällä.",
"empty_column.account_unavailable": "Profiilia ei löydy",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Leitiúrslit",
"emoji_button.symbols": "Ímyndir",
"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_timeline": "Einki uppslag her!",
"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.badges.bot": "Bot",
"account.badges.group": "Otù",
"account.cancel_follow_request": "Withdraw follow request",
"account.follow": "Soro",
"account.followers": "Ndị na-eso",
"account.following": "Na-eso",
"account.follows_you": "Na-eso gị",
"account.mute": "Mee ogbi @{name}",
"account.unfollow": "Kwụsị iso",
@ -11,16 +14,20 @@
"audio.hide": "Zoo ụda",
"bundle_column_error.retry": "Nwaa ọzọ",
"bundle_column_error.routing.title": "404",
"bundle_modal_error.close": "Mechie",
"bundle_modal_error.retry": "Nwaa ọzọ",
"column.about": "Maka",
"column.blocks": "Ojiarụ egbochiri",
"column.bookmarks": "Ebenrụtụakā",
"column.home": "Be",
"column.lists": "Ndepụta",
"column.pins": "Pinned post",
"column_header.pin": "Gbado na profaịlụ gị",
"column_subheading.settings": "Mwube",
"community.column_settings.media_only": "Media only",
"compose.language.change": "Gbanwee 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.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?",
@ -32,7 +39,10 @@
"confirmations.delete.message": "Are you sure you want to delete this status?",
"confirmations.delete_list.confirm": "Hichapụ",
"confirmations.domain_block.confirm": "Hide entire domain",
"confirmations.edit.confirm": "Dezie",
"confirmations.mute.confirm": "Mee ogbi",
"confirmations.reply.confirm": "Zaa",
"confirmations.unfollow.confirm": "Kwụsị iso",
"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_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.up": "to move up in the list",
"lists.delete": "Hichapụ ndepụta",
"lists.edit": "Dezie ndepụta",
"lists.subheading": "Ndepụta gị",
"loading_indicator.label": "Na-adọnye...",
"navigation_bar.about": "Maka",
@ -100,20 +111,27 @@
"privacy.change": "Adjust status privacy",
"privacy.direct.short": "Direct",
"privacy.private.short": "Followers-only",
"relative_time.full.just_now": "kịta",
"relative_time.just_now": "kịta",
"relative_time.today": "taa",
"reply_indicator.cancel": "Kagbuo",
"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.submit": "Submit report",
"report.target": "Report {target}",
"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.learn_more": "Mụtakwuo",
"sign_in_banner.sign_in": "Sign in",
"status.admin_status": "Open this status in the moderation interface",
"status.bookmark": "Kee ebenrụtụakā",
"status.copy": "Copy link to status",
"status.delete": "Hichapụ",
"status.edit": "Dezie",
"status.edited_x_times": "Edited {count, plural, one {# time} other {# times}}",
"status.open": "Expand this status",
"status.remove_bookmark": "Wepu ebenrụtụakā",

View file

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

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "검색 결과",
"emoji_button.symbols": "기호",
"emoji_button.travel": "여행과 장소",
"empty_column.account_hides_collections": "이 사용자는 이 정보를 사용할 수 없도록 설정했습니다",
"empty_column.account_suspended": "계정 정지됨",
"empty_column.account_timeline": "이곳에는 게시물이 없습니다!",
"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.badges.bot": "Automatizuotas",
"account.badges.group": "Grupė",
"account.block": "Užblokuoti @{name}",
"account.block": "Blokuoti @{name}",
"account.block_domain": "Blokuoti domeną {domain}",
"account.block_short": "Blokuoti",
"account.blocked": "Užblokuota",
@ -25,11 +25,20 @@
"account.disable_notifications": "Nustoti man pranešti, kai @{name} paskelbia",
"account.domain_blocked": "Užblokuotas domenas",
"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.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.in_memoriam": "Atminimui.",
"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.media": "Medija",
"account.mute": "Užtildyti @{name}",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Søkeresultat",
"emoji_button.symbols": "Symboler",
"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_timeline": "Ingen innlegg her!",
"empty_column.account_unavailable": "Profilen er utilgjengelig",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Resultado da pesquisa",
"emoji_button.symbols": "Símbolos",
"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_timeline": "Nada aqui.",
"empty_column.account_unavailable": "Perfil indisponível",

View file

@ -222,6 +222,7 @@
"emoji_button.search_results": "Resultados da pesquisa",
"emoji_button.symbols": "Símbolos",
"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_timeline": "Sem publicações por aqui!",
"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.symbols": "Biểu tượng",
"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_timeline": "Chưa có tút nào!",
"empty_column.account_unavailable": "Tài khoản bị đình chỉ",

View file

@ -5508,6 +5508,7 @@ a.status-card {
.modal-root__modal {
pointer-events: auto;
user-select: text;
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
# 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?)
authorize([:admin, status], :update?)

View file

@ -77,7 +77,7 @@ module StatusSearchConcern
properties << 'media' if with_media?
properties << 'poll' if with_poll?
properties << 'link' if with_preview_card?
properties << 'embed' if preview_cards.any?(&:video?)
properties << 'embed' if preview_card&.video?
properties << 'sensitive' if sensitive?
properties << 'reply' if reply?
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 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_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?
# This can be set by the status when retrieving the preview card using the join model
attr_accessor :original_url
def appropriate_for_trends?
link? && article? && title.present? && description.present? && image.present? && provider_name.present?
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_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 :status_stat, inverse_of: :status
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 `dependent: destroy` callbacks remove relevant records
before_destroy :unlink_from_conversations!, prepend: true
before_destroy :reset_preview_card!
cache_associated :application,
:media_attachments,
:conversation,
:status_stat,
:tags,
:preview_cards,
:preloadable_poll,
:reference_objects,
:scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role],
active_mentions: { account: :account_stat },
reblog: [
:application,
:tags,
:preview_cards,
:media_attachments,
:conversation,
:status_stat,
:preloadable_poll,
:reference_objects,
:scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role],
active_mentions: { account: :account_stat },
],
@ -207,6 +208,7 @@ class Status < ApplicationRecord
:preloadable_poll,
:reference_objects,
:scheduled_expiration_status,
preview_cards_status: [:preview_card],
account: [:account_stat, user: :role],
active_mentions: { account: :account_stat },
],
@ -277,7 +279,11 @@ class Status < ApplicationRecord
end
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
def hidden?
@ -300,7 +306,7 @@ class Status < ApplicationRecord
end
def with_preview_card?
preview_cards.any?
preview_cards_status.present?
end
def with_poll?

View file

@ -54,9 +54,7 @@ class Trends::Links < Trends::Base
!(original_status.account.silenced? || status.account.silenced?) &&
!(original_status.spoiler_text? || original_status.sensitive?)
original_status.preview_cards.each do |preview_card|
add(preview_card, status.account_id, at_time) if preview_card.appropriate_for_trends?
end
add(original_status.preview_card, status.account_id, at_time) if original_status.preview_card&.appropriate_for_trends?
end
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 :events, presence: true
validate :validate_events
validate :events_validation_error, if: :invalid_events?
validate :validate_permissions
validate :validate_template
before_validation :strip_events
normalizes :events, with: ->(events) { events.filter_map { |event| event.strip.presence } }
before_validation :generate_secret
def rotate_secret!
@ -69,8 +69,12 @@ class Webhook < ApplicationRecord
private
def validate_events
errors.add(:events, :invalid) if events.any? { |e| EVENTS.exclude?(e) }
def events_validation_error
errors.add(:events, :invalid)
end
def invalid_events?
events.blank? || events.difference(EVENTS).any?
end
def validate_permissions
@ -88,10 +92,6 @@ class Webhook < ApplicationRecord
end
end
def strip_events
self.events = events.filter_map { |str| str.strip.presence } if events.present?
end
def generate_secret
self.secret = SecureRandom.hex(20) if secret.blank?
end

View file

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

View file

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

View file

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

View file

@ -1,12 +1,14 @@
# frozen_string_literal: true
class AppSignUpService < BaseService
include RegistrationHelper
def call(app, remote_ip, params)
@app = app
@remote_ip = remote_ip
@params = params
raise Mastodon::NotPermittedError unless allowed_registrations?
raise Mastodon::NotPermittedError unless allowed_registration?(remote_ip, invite)
ApplicationRecord.transaction do
create_user!
@ -34,8 +36,12 @@ class AppSignUpService < BaseService
)
end
def invite
Invite.find_by(code: @params[:invite_code]) if @params[:invite_code].present?
end
def user_params
@params.slice(:email, :password, :agreement, :locale, :time_zone)
@params.slice(:email, :password, :agreement, :locale, :time_zone, :invite_code)
end
def account_params
@ -45,24 +51,4 @@ class AppSignUpService < BaseService
def invite_request_params
{ text: @params[:reason] }
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

View file

@ -20,7 +20,7 @@ class FetchLinkCardService < BaseService
@status = status
@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
@ -63,9 +63,9 @@ class FetchLinkCardService < BaseService
def attach_card
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)
Trends.links.register(@status)
end

View file

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

View file

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

View file

@ -1,18 +1,143 @@
---
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:
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:
buttons:
authorize: Įgalinti
deny: Atmesti
error:
title: Įvyko klaida.
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>"
review_permissions: Peržiūrėti leidimus
title: Reikalingas įgaliojimas
show:
title: Nukopijuok šį įgaliojimo kodą ir įklijuok jį į programėlę.
authorized_applications:
buttons:
revoke: Naikinti
confirmations:
revoke: Ar esi įsitikinęs (-usi)?
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:
access:
read: Tik skaitymo prieiga
read/write: Skaitymo ir rašymo prieiga
write: Tik rašymo prieiga
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
bookmarks: Žymės
conversations: Pokalbiai
crypto: Galo iki galo užšifravimas
favourites: Mėgstami
filters: Filtrai
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
layouts:
admin:
@ -37,6 +162,7 @@ lt:
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: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ą
follow: modifikuoti paskyros santykius
push: gauti tavo stumiamuosius pranešimus

View file

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

View file

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

View file

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

View file

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

View file

@ -1689,8 +1689,8 @@ ko:
keep_polls_hint: 설문을 삭제하지 않았음
keep_self_bookmark: 북마크한 게시물 유지
keep_self_bookmark_hint: 북마크한 본인의 게시물을 삭제하지 않습니다
keep_self_fav: 마음에 들어한 게시물 유지
keep_self_fav_hint: 내 스스로 마음에 들어한 본인의 게시물을 삭제하지 않습니다
keep_self_fav: 내가 좋아요한 게시물 유지
keep_self_fav_hint: 스스로 좋아요를 누른 본인의 게시물을 삭제하지 않습니다
min_age:
'1209600': 2
'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".
title: Sikkerheitssjekk
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.
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.

View file

@ -772,6 +772,11 @@
approved: Godkjenning kreves for påmelding
none: Ingen 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
site_uploads:
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".
title: Sikkerhetskontroll
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.
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.
@ -1739,6 +1752,10 @@
month: "%b %Y"
time: "%H:%M"
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:
add: Legg til
disable: Skru av

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
module.exports = {
test: /\.svg$/,
include: /node_modules\/@material-symbols/,
include: [/node_modules\/@material-symbols/, /svg-icons/],
issuer: /\.[jt]sx?$/,
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
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|
t.bigint "account_id"
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 "avatar_file_name"
t.string "avatar_content_type"
t.integer "avatar_file_size"
t.bigint "avatar_file_size"
t.datetime "avatar_updated_at", precision: nil
t.string "header_file_name"
t.string "header_content_type"
t.integer "header_file_size"
t.bigint "header_file_size"
t.datetime "header_updated_at", precision: nil
t.string "avatar_remote_url"
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 "header_storage_schema_version"
t.string "devices_url"
t.integer "suspension_origin"
t.datetime "sensitized_at", precision: nil
t.integer "suspension_origin"
t.boolean "trendable"
t.datetime "reviewed_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 ["list_id"], name: "index_antennas_on_list_id"
t.index ["stl"], name: "index_antennas_on_stl"
t.index ["with_media_only"], name: "index_antennas_on_with_media_only"
end
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 "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.bigint "image_file_size"
t.datetime "image_updated_at", precision: nil
t.datetime "created_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.string "data_file_name"
t.string "data_content_type"
t.integer "data_file_size"
t.bigint "data_file_size"
t.datetime "data_updated_at", precision: nil
t.bigint "account_id", 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
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.integer "severity", default: 0, null: false
t.datetime "expires_at", precision: nil
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
end
@ -808,7 +800,7 @@ ActiveRecord::Schema[7.1].define(version: 2023_11_05_225839) do
t.bigint "status_id"
t.string "file_file_name"
t.string "file_content_type"
t.integer "file_file_size"
t.bigint "file_file_size"
t.datetime "file_updated_at", precision: nil
t.string "remote_url", default: "", 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.string "thumbnail_file_name"
t.string "thumbnail_content_type"
t.integer "thumbnail_file_size"
t.datetime "thumbnail_updated_at", precision: nil
t.bigint "thumbnail_file_size"
t.datetime "thumbnail_updated_at"
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 ["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 "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.bigint "image_file_size"
t.datetime "image_updated_at", precision: nil
t.integer "type", default: 0, 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|
t.bigint "preview_card_id", null: false
t.bigint "status_id", null: false
t.string "url"
end
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 "file_file_name"
t.string "file_content_type"
t.integer "file_file_size"
t.bigint "file_file_size"
t.datetime "file_updated_at", precision: nil
t.json "meta"
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|
t.bigint "account_id", null: false
t.bigint "status_id", null: false
t.datetime "created_at", precision: nil, default: -> { "now()" }, null: false
t.datetime "updated_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: -> { "CURRENT_TIMESTAMP" }, null: false
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"
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.string "emoji_reactions"
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 "status_referred_by_count", default: 0, null: false
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_deletion_requests", "accounts", 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", on_delete: :cascade
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
transformIgnorePatterns: ['/node_modules/(?!(redent|strip-indent)/)'],
coverageDirectory: '<rootDir>/coverage',
moduleDirectories: ['<rootDir>/node_modules', '<rootDir>/app/javascript'],
moduleDirectories: ['node_modules', '<rootDir>/app/javascript'],
moduleNameMapper: {
'\\.svg$': '<rootDir>/app/javascript/__mocks__/svg.js',
},

View file

@ -69,7 +69,7 @@ namespace :tests do
exit(1)
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'
exit(1)
end

View file

@ -181,7 +181,7 @@
"@types/react-dom": "^18.2.4",
"@types/react-helmet": "^6.1.6",
"@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-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
@ -205,7 +205,7 @@
"eslint-plugin-formatjs": "^4.10.1",
"eslint-plugin-import": "~2.29.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-promise": "~6.1.1",
"eslint-plugin-react": "~7.33.0",

View file

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

View file

@ -2,14 +2,44 @@
require 'rails_helper'
describe Api::V1::Trends::LinksController do
RSpec.describe Api::V1::Trends::LinksController do
render_views
describe 'GET #index' do
it 'returns http success' do
get :index
around do |example|
previous = Setting.trends
example.run
Setting.trends = previous
end
expect(response).to have_http_status(200)
context 'when trends are disabled' do
before { Setting.trends = false }
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
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

View file

@ -2,14 +2,44 @@
require 'rails_helper'
describe Api::V1::Trends::StatusesController do
RSpec.describe Api::V1::Trends::StatusesController do
render_views
describe 'GET #index' do
it 'returns http success' do
get :index
around do |example|
previous = Setting.trends
example.run
Setting.trends = previous
end
expect(response).to have_http_status(200)
context 'when trends are disabled' do
before { Setting.trends = false }
it 'returns http success' do
get :index
expect(response).to have_http_status(200)
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

View file

@ -6,16 +6,41 @@ RSpec.describe Api::V1::Trends::TagsController do
render_views
describe 'GET #index' do
before do
Fabricate.times(10, :tag).each do |tag|
10.times { |i| Trends.tags.add(tag, i) }
end
get :index
around do |example|
previous = Setting.trends
example.run
Setting.trends = previous
end
it 'returns http success' do
expect(response).to have_http_status(200)
context 'when trends are disabled' do
before { Setting.trends = false }
it 'returns http success' do
get :index
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

View file

@ -49,10 +49,12 @@ describe MediaComponentHelper do
end
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) }
before do
PreviewCardsStatus.create(status: status, preview_card: Fabricate(:preview_card))
without_partial_double_verification do
allow(helper).to receive(:current_account).and_return(status.account)
end

View file

@ -5,6 +5,37 @@ require 'rails_helper'
RSpec.describe Webhook do
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
it 'changes the secret' do
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,46 +10,72 @@ RSpec.describe AppSignUpService, type: :service do
let(:remote_ip) { IPAddr.new('198.0.2.1') }
describe '#call' do
it 'returns nil when registrations are closed' do
tmp = Setting.registrations_mode
Setting.registrations_mode = 'none'
expect { subject.call(app, remote_ip, good_params) }.to raise_error Mastodon::NotPermittedError
Setting.registrations_mode = tmp
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
Setting.registrations_mode = 'none'
example.run
Setting.registrations_mode = tmp
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
expect { subject.call(app, remote_ip, {}) }.to raise_error ActiveRecord::RecordInvalid
end
it 'creates an unconfirmed user with access token' 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.confirmed?).to be false
end
it_behaves_like 'successful registration'
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
it 'creates an account with invite request text' do
access_token = subject.call(app, remote_ip, good_params.merge(reason: 'Foo bar'))
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.invite_request&.text).to eq 'Foo bar'
context 'when given an 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'))
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.invite_request&.text).to eq 'Foo bar'
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') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('SJISのページ')
expect(status.preview_card.title).to eq('SJISのページ')
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') }
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
@ -137,7 +137,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/koi8-r') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
expect(status.preview_card.title).to eq('Московя начинаетъ только въ XVI ст. привлекать внимане иностранцевъ.')
end
end
@ -145,7 +145,7 @@ RSpec.describe FetchLinkCardService, type: :service do
let(:status) { Fabricate(:status, text: 'Check out http://example.com/windows-1251') }
it 'decodes the HTML' do
expect(status.preview_cards.first.title).to eq('сэмпл текст')
expect(status.preview_card.title).to eq('сэмпл текст')
end
end
@ -253,11 +253,21 @@ RSpec.describe FetchLinkCardService, type: :service do
expect(status.preview_card.title).to eq 'Hello world'
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
context 'with a remote status' do
let(:account) { Fabricate(:account, domain: 'example.com') }
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>
#<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. !
@ -272,6 +282,14 @@ RSpec.describe FetchLinkCardService, type: :service do
it 'ignores URLs to hashtags' do
expect(a_request(:get, 'https://quitter.se/tag/wannacry')).to_not have_been_made
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
context 'with a remote status of reference' do

View file

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

1491
yarn.lock

File diff suppressed because it is too large Load diff