Merge branch 'kb_migration' into kb_migration_development

This commit is contained in:
KMY 2023-04-20 17:43:47 +09:00
commit 51cac0760f
99 changed files with 1077 additions and 365 deletions

View file

@ -7,8 +7,9 @@ class AccountsController < ApplicationController
include AccountControllerConcern
include SignatureAuthentication
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { [:json, :rss].include?(request.format&.to_sym) }
skip_before_action :require_functional!, unless: :whitelist_mode?

View file

@ -7,10 +7,6 @@ class ActivityPub::BaseController < Api::BaseController
private
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode?
end
def skip_temporary_suspension_response?
false
end

View file

@ -4,11 +4,12 @@ class ActivityPub::CollectionsController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_items
before_action :set_size
before_action :set_type
before_action :set_cache_headers
def show
expires_in 3.minutes, public: public_fetch_mode?

View file

@ -4,9 +4,10 @@ class ActivityPub::FollowersSynchronizationsController < ActivityPub::BaseContro
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!
before_action :set_items
before_action :set_cache_headers
def show
expires_in 0, public: false

View file

@ -6,9 +6,10 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
include SignatureVerification
include AccountOwnedConcern
vary_by -> { 'Signature' if authorized_fetch_mode? || page_requested? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_statuses
before_action :set_cache_headers
def show
if page_requested?
@ -16,6 +17,7 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
else
expires_in(3.minutes, public: public_fetch_mode?)
end
render json: outbox_presenter, serializer: ActivityPub::OutboxSerializer, adapter: ActivityPub::Adapter, content_type: 'application/activity+json'
end
@ -80,8 +82,4 @@ class ActivityPub::OutboxesController < ActivityPub::BaseController
def set_account
@account = params[:account_username].present? ? Account.find_local!(username_param) : Account.representative
end
def set_cache_headers
response.headers['Vary'] = 'Signature' if authorized_fetch_mode? || page_requested?
end
end

View file

@ -7,9 +7,10 @@ class ActivityPub::RepliesController < ActivityPub::BaseController
DESCENDANTS_LIMIT = 60
vary_by -> { 'Signature' if authorized_fetch_mode? }
before_action :require_account_signature!, if: :authorized_fetch_mode?
before_action :set_status
before_action :set_cache_headers
before_action :set_replies
def index

View file

@ -8,6 +8,8 @@ module Admin
layout 'admin'
before_action :set_body_classes
before_action :set_cache_headers
after_action :verify_authorized
private
@ -16,6 +18,10 @@ module Admin
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
def set_user
@user = Account.find(params[:account_id]).user || raise(ActiveRecord::RecordNotFound)
end

View file

@ -12,7 +12,7 @@ class Api::BaseController < ApplicationController
before_action :require_authenticated_user!, if: :disallow_unauthenticated_api_access?
before_action :require_not_suspended!
before_action :set_cache_headers
before_action :set_cache_control_defaults
protect_from_forgery with: :null_session
@ -148,8 +148,8 @@ class Api::BaseController < ApplicationController
doorkeeper_authorize!(*scopes) if doorkeeper_token
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
def set_cache_control_defaults
response.cache_control.replace(private: true, no_store: true)
end
def disallow_unauthenticated_api_access?

View file

@ -0,0 +1,72 @@
# frozen_string_literal: true
class Api::V1::Admin::Trends::Links::PreviewCardProvidersController < Api::BaseController
include Authorization
LIMIT = 100
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
before_action :set_providers, only: :index
after_action :verify_authorized
after_action :insert_pagination_headers, only: :index
PAGINATION_PARAMS = %i(limit).freeze
def index
authorize :preview_card_provider, :index?
render json: @providers, each_serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
end
def approve
authorize :preview_card_provider, :review?
provider = PreviewCardProvider.find(params[:id])
provider.update(trendable: true, reviewed_at: Time.now.utc)
render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
end
def reject
authorize :preview_card_provider, :review?
provider = PreviewCardProvider.find(params[:id])
provider.update(trendable: false, reviewed_at: Time.now.utc)
render json: provider, serializer: REST::Admin::Trends::Links::PreviewCardProviderSerializer
end
private
def set_providers
@providers = PreviewCardProvider.all.to_a_paginated_by_id(limit_param(LIMIT), params_slice(:max_id, :since_id, :min_id))
end
def insert_pagination_headers
set_pagination_headers(next_path, prev_path)
end
def next_path
api_v1_admin_trends_links_preview_card_providers_url(pagination_params(max_id: pagination_max_id)) if records_continue?
end
def prev_path
api_v1_admin_trends_links_preview_card_providers_url(pagination_params(min_id: pagination_since_id)) unless @providers.empty?
end
def pagination_max_id
@providers.last.id
end
def pagination_since_id
@providers.first.id
end
def records_continue?
@providers.size == limit_param(LIMIT)
end
def pagination_params(core_params)
params.slice(*PAGINATION_PARAMS).permit(*PAGINATION_PARAMS).merge(core_params)
end
end

View file

@ -1,7 +1,36 @@
# frozen_string_literal: true
class Api::V1::Admin::Trends::LinksController < Api::V1::Trends::LinksController
before_action -> { authorize_if_got_token! :'admin:read' }
include Authorization
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
after_action :verify_authorized, except: :index
def index
if current_user&.can?(:manage_taxonomies)
render json: @links, each_serializer: REST::Admin::Trends::LinkSerializer
else
super
end
end
def approve
authorize :preview_card, :review?
link = PreviewCard.find(params[:id])
link.update(trendable: true)
render json: link, serializer: REST::Admin::Trends::LinkSerializer
end
def reject
authorize :preview_card, :review?
link = PreviewCard.find(params[:id])
link.update(trendable: false)
render json: link, serializer: REST::Admin::Trends::LinkSerializer
end
private

View file

@ -1,7 +1,36 @@
# frozen_string_literal: true
class Api::V1::Admin::Trends::StatusesController < Api::V1::Trends::StatusesController
before_action -> { authorize_if_got_token! :'admin:read' }
include Authorization
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
after_action :verify_authorized, except: :index
def index
if current_user&.can?(:manage_taxonomies)
render json: @statuses, each_serializer: REST::Admin::Trends::StatusSerializer
else
super
end
end
def approve
authorize [:admin, :status], :review?
status = Status.find(params[:id])
status.update(trendable: true)
render json: status, serializer: REST::Admin::Trends::StatusSerializer
end
def reject
authorize [:admin, :status], :review?
status = Status.find(params[:id])
status.update(trendable: false)
render json: status, serializer: REST::Admin::Trends::StatusSerializer
end
private

View file

@ -1,7 +1,12 @@
# frozen_string_literal: true
class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
before_action -> { authorize_if_got_token! :'admin:read' }
include Authorization
before_action -> { authorize_if_got_token! :'admin:read' }, only: :index
before_action -> { authorize_if_got_token! :'admin:write' }, except: :index
after_action :verify_authorized, except: :index
def index
if current_user&.can?(:manage_taxonomies)
@ -11,6 +16,22 @@ class Api::V1::Admin::Trends::TagsController < Api::V1::Trends::TagsController
end
end
def approve
authorize :tag, :review?
tag = Tag.find(params[:id])
tag.update(trendable: true, reviewed_at: Time.now.utc)
render json: tag, serializer: REST::Admin::TagSerializer
end
def reject
authorize :tag, :review?
tag = Tag.find(params[:id])
tag.update(trendable: false, reviewed_at: Time.now.utc)
render json: tag, serializer: REST::Admin::TagSerializer
end
private
def enabled?

View file

@ -1,8 +1,6 @@
# frozen_string_literal: true
class Api::V1::CustomEmojisController < Api::BaseController
skip_before_action :set_cache_headers
def index
expires_in 3.minutes, public: true
render_with_cache(each_serializer: REST::CustomEmojiSerializer) { CustomEmoji.listed.includes(:category) }

View file

@ -3,7 +3,6 @@
class Api::V1::Instances::ActivityController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
def show

View file

@ -3,7 +3,6 @@
class Api::V1::Instances::PeersController < Api::BaseController
before_action :require_enabled_api!
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
def index

View file

@ -1,7 +1,6 @@
# frozen_string_literal: true
class Api::V1::InstancesController < Api::BaseController
skip_before_action :set_cache_headers
skip_before_action :require_authenticated_user!, unless: :whitelist_mode?
def show

View file

@ -152,6 +152,6 @@ class Auth::RegistrationsController < Devise::RegistrationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -155,8 +155,16 @@ module CacheConcern
end
end
class_methods do
def vary_by(value)
before_action do |controller|
response.headers['Vary'] = value.respond_to?(:call) ? controller.instance_exec(&value) : value
end
end
end
def render_with_cache(**options)
raise ArgumentError, 'only JSON render calls are supported' unless options.key?(:json) || block_given?
raise ArgumentError, 'Only JSON render calls are supported' unless options.key?(:json) || block_given?
key = options.delete(:key) || [[params[:controller], params[:action]].join('/'), options[:json].respond_to?(:cache_key) ? options[:json].cache_key : nil, options[:fields].nil? ? nil : options[:fields].join(',')].compact.join(':')
expires_in = options.delete(:expires_in) || 3.minutes
@ -176,10 +184,6 @@ module CacheConcern
end
end
def set_cache_headers
response.headers['Vary'] = public_fetch_mode? ? 'Accept' : 'Accept, Signature'
end
def cache_collection(raw, klass)
return raw unless klass.respond_to?(:with_includes)

View file

@ -1,18 +1,8 @@
# frozen_string_literal: true
class CustomCssController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!
skip_before_action :update_user_sign_in
skip_before_action :set_session_activity
skip_around_action :set_locale
before_action :set_cache_headers
class CustomCssController < ActionController::Base # rubocop:disable Rails/ApplicationController
def show
expires_in 3.minutes, public: true
request.session_options[:skip] = true
render content_type: 'text/css'
end
end

View file

@ -9,10 +9,15 @@ class Disputes::BaseController < ApplicationController
before_action :set_body_classes
before_action :authenticate_user!
before_action :set_cache_headers
private
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -2,15 +2,12 @@
class EmojisController < ApplicationController
before_action :set_emoji
before_action :set_cache_headers
vary_by -> { 'Signature' if authorized_fetch_mode? }
def show
respond_to do |format|
format.json do
expires_in 3.minutes, public: true
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
end
end
expires_in 3.minutes, public: true
render_with_cache json: @emoji, content_type: 'application/activity+json', serializer: ActivityPub::EmojiSerializer, adapter: ActivityPub::Adapter
end
private

View file

@ -7,6 +7,7 @@ class Filters::StatusesController < ApplicationController
before_action :set_filter
before_action :set_status_filters
before_action :set_body_classes
before_action :set_cache_headers
PER_PAGE = 20
@ -44,4 +45,8 @@ class Filters::StatusesController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -6,6 +6,7 @@ class FiltersController < ApplicationController
before_action :authenticate_user!
before_action :set_filter, only: [:edit, :update, :destroy]
before_action :set_body_classes
before_action :set_cache_headers
def index
@filters = current_account.custom_filters.includes(:keywords, :statuses).order(:phrase)
@ -54,4 +55,8 @@ class FiltersController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -5,8 +5,9 @@ class FollowerAccountsController < ApplicationController
include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, unless: :whitelist_mode?

View file

@ -5,8 +5,9 @@ class FollowingAccountsController < ApplicationController
include SignatureVerification
include WebAppControllerConcern
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_cache_headers
skip_around_action :set_locale, if: -> { request.format == :json }
skip_before_action :require_functional!, unless: :whitelist_mode?

View file

@ -7,6 +7,7 @@ class InvitesController < ApplicationController
before_action :authenticate_user!
before_action :set_body_classes
before_action :set_cache_headers
def index
authorize :invite, :create?
@ -49,4 +50,8 @@ class InvitesController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -1,9 +1,6 @@
# frozen_string_literal: true
class ManifestsController < ApplicationController
skip_before_action :store_current_location
skip_before_action :require_functional!
class ManifestsController < ActionController::Base # rubocop:disable Rails/ApplicationController
def show
expires_in 3.minutes, public: true
render json: InstancePresenter.new, serializer: ManifestSerializer, root: 'instance'

View file

@ -34,6 +34,6 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -7,6 +7,7 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
before_action :authenticate_resource_owner!
before_action :require_not_suspended!, only: :destroy
before_action :set_body_classes
before_action :set_cache_headers
skip_before_action :require_functional!
@ -30,4 +31,8 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
def require_not_suspended!
forbidden if current_account.suspended?
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -7,6 +7,7 @@ class RelationshipsController < ApplicationController
before_action :set_accounts, only: :show
before_action :set_relationships, only: :show
before_action :set_body_classes
before_action :set_cache_headers
helper_method :following_relationship?, :followed_by_relationship?, :mutual_relationship?
@ -70,4 +71,8 @@ class RelationshipsController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -14,7 +14,7 @@ class Settings::BaseController < ApplicationController
end
def set_cache_headers
response.headers['Cache-Control'] = 'private, no-store'
response.cache_control.replace(private: true, no_store: true)
end
def require_not_suspended!

View file

@ -6,6 +6,7 @@ class StatusesCleanupController < ApplicationController
before_action :authenticate_user!
before_action :set_policy
before_action :set_body_classes
before_action :set_cache_headers
def show; end
@ -36,4 +37,8 @@ class StatusesCleanupController < ApplicationController
def set_body_classes
@body_classes = 'admin'
end
def set_cache_headers
response.cache_control.replace(private: true, no_store: true)
end
end

View file

@ -6,11 +6,12 @@ class StatusesController < ApplicationController
include Authorization
include AccountOwnedConcern
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
before_action :require_account_signature!, only: [:show, :activity], if: -> { request.format == :json && authorized_fetch_mode? }
before_action :set_status
before_action :set_instance_presenter
before_action :redirect_to_original, only: :show
before_action :set_cache_headers
before_action :set_body_classes, only: :embed
after_action :set_link_headers

View file

@ -7,6 +7,8 @@ class TagsController < ApplicationController
PAGE_SIZE = 20
PAGE_SIZE_MAX = 200
vary_by -> { public_fetch_mode? ? 'Accept' : 'Accept, Signature' }
before_action :require_account_signature!, if: -> { request.format == :json && authorized_fetch_mode? }
before_action :authenticate_user!, if: :whitelist_mode?
before_action :set_local

View file

@ -1,11 +1,9 @@
# frozen_string_literal: true
module WellKnown
class HostMetaController < ActionController::Base
class HostMetaController < ActionController::Base # rubocop:disable Rails/ApplicationController
include RoutingHelper
before_action { response.headers['Vary'] = 'Accept' }
def show
@webfinger_template = "#{webfinger_url}?resource={uri}"
expires_in 3.days, public: true

View file

@ -1,11 +1,9 @@
# frozen_string_literal: true
module WellKnown
class NodeInfoController < ActionController::Base
class NodeInfoController < ActionController::Base # rubocop:disable Rails/ApplicationController
include CacheConcern
before_action { response.headers['Vary'] = 'Accept' }
def index
expires_in 3.days, public: true
render_with_cache json: {}, serializer: NodeInfo::DiscoverySerializer, adapter: NodeInfo::Adapter, expires_in: 3.days, root: 'nodeinfo'

View file

@ -1,7 +1,7 @@
# frozen_string_literal: true
module WellKnown
class WebfingerController < ActionController::Base
class WebfingerController < ActionController::Base # rubocop:disable Rails/ApplicationController
include RoutingHelper
before_action :set_account
@ -34,7 +34,12 @@ module WellKnown
end
def check_account_suspension
expires_in(3.minutes, public: true) && gone if @account.suspended_permanently?
gone if @account.suspended_permanently?
end
def gone
expires_in(3.minutes, public: true)
head 410
end
def bad_request
@ -46,9 +51,5 @@ module WellKnown
expires_in(3.minutes, public: true)
head 404
end
def gone
head 410
end
end
end

View file

@ -3,6 +3,8 @@
exports[`<AvatarOverlay renders a overlay avatar 1`] = `
<div
className="account__avatar-overlay"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
{
"height": 46,
@ -15,8 +17,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
>
<div
className="account__avatar"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
{
"height": "36px",
@ -35,8 +35,6 @@ exports[`<AvatarOverlay renders a overlay avatar 1`] = `
>
<div
className="account__avatar"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
style={
{
"height": "24px",

View file

@ -1,76 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ShortNumber from 'mastodon/components/short_number';
import TransitionMotion from 'react-motion/lib/TransitionMotion';
import spring from 'react-motion/lib/spring';
import { reduceMotion } from 'mastodon/initial_state';
const obfuscatedCount = count => {
if (count < 0) {
return 0;
} else if (count <= 1) {
return count;
} else {
return '1+';
}
};
export default class AnimatedNumber extends React.PureComponent {
static propTypes = {
value: PropTypes.number.isRequired,
obfuscate: PropTypes.bool,
};
state = {
direction: 1,
};
componentWillReceiveProps (nextProps) {
if (nextProps.value > this.props.value) {
this.setState({ direction: 1 });
} else if (nextProps.value < this.props.value) {
this.setState({ direction: -1 });
}
}
willEnter = () => {
const { direction } = this.state;
return { y: -1 * direction };
};
willLeave = () => {
const { direction } = this.state;
return { y: spring(1 * direction, { damping: 35, stiffness: 400 }) };
};
render () {
const { value, obfuscate } = this.props;
const { direction } = this.state;
if (reduceMotion) {
return obfuscate ? obfuscatedCount(value) : <ShortNumber value={value} />;
}
const styles = [{
key: `${value}`,
data: value,
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
}];
return (
<TransitionMotion styles={styles} willEnter={this.willEnter} willLeave={this.willLeave}>
{items => (
<span className='animated-number'>
{items.map(({ key, data, style }) => (
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
))}
</span>
)}
</TransitionMotion>
);
}
}

View file

@ -0,0 +1,58 @@
import React, { useCallback, useState } from 'react';
import ShortNumber from './short_number';
import { TransitionMotion, spring } from 'react-motion';
import { reduceMotion } from '../initial_state';
const obfuscatedCount = (count: number) => {
if (count < 0) {
return 0;
} else if (count <= 1) {
return count;
} else {
return '1+';
}
};
type Props = {
value: number;
obfuscate?: boolean;
}
export const AnimatedNumber: React.FC<Props> = ({
value,
obfuscate,
})=> {
const [previousValue, setPreviousValue] = useState(value);
const [direction, setDirection] = useState<1|-1>(1);
if (previousValue !== value) {
setPreviousValue(value);
setDirection(value > previousValue ? 1 : -1);
}
const willEnter = useCallback(() => ({ y: -1 * direction }), [direction]);
const willLeave = useCallback(() => ({ y: spring(1 * direction, { damping: 35, stiffness: 400 }) }), [direction]);
if (reduceMotion) {
return obfuscate ? <>{obfuscatedCount(value)}</> : <ShortNumber value={value} />;
}
const styles = [{
key: `${value}`,
data: value,
style: { y: spring(0, { damping: 35, stiffness: 400 }) },
}];
return (
<TransitionMotion styles={styles} willEnter={willEnter} willLeave={willLeave}>
{items => (
<span className='animated-number'>
{items.map(({ key, data, style }) => (
<span key={key} style={{ position: (direction * style.y) > 0 ? 'absolute' : 'static', transform: `translateY(${style.y * 100}%)` }}>{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}</span>
))}
</span>
)}
</TransitionMotion>
);
};
export default AnimatedNumber;

View file

@ -1,51 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { autoPlayGif } from '../initial_state';
import Avatar from './avatar';
export default class AvatarOverlay extends React.PureComponent {
static propTypes = {
account: ImmutablePropTypes.map.isRequired,
friend: ImmutablePropTypes.map.isRequired,
animate: PropTypes.bool,
size: PropTypes.number,
baseSize: PropTypes.number,
overlaySize: PropTypes.number,
};
static defaultProps = {
animate: autoPlayGif,
size: 46,
baseSize: 36,
overlaySize: 24,
};
state = {
hovering: false,
};
handleMouseEnter = () => {
if (this.props.animate) return;
this.setState({ hovering: true });
};
handleMouseLeave = () => {
if (this.props.animate) return;
this.setState({ hovering: false });
};
render() {
const { account, friend, animate, size, baseSize, overlaySize } = this.props;
const { hovering } = this.state;
return (
<div className='account__avatar-overlay' style={{ width: size, height: size }}>
<div className='account__avatar-overlay-base'><Avatar animate={hovering || animate} account={account} size={baseSize} /></div>
<div className='account__avatar-overlay-overlay'><Avatar animate={hovering || animate} account={friend} size={overlaySize} /></div>
</div>
);
}
}

View file

@ -0,0 +1,51 @@
import React from 'react';
import type { Account } from '../../types/resources';
import { useHovering } from '../../hooks/useHovering';
import { autoPlayGif } from '../initial_state';
type Props = {
account: Account;
friend: Account;
size?: number;
baseSize?: number;
overlaySize?: number;
};
export const AvatarOverlay: React.FC<Props> = ({
account,
friend,
size = 46,
baseSize = 36,
overlaySize = 24,
}) => {
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(autoPlayGif);
const accountSrc = hovering ? account?.get('avatar') : account?.get('avatar_static');
const friendSrc = hovering ? friend?.get('avatar') : friend?.get('avatar_static');
return (
<div
className='account__avatar-overlay' style={{ width: size, height: size }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<div className='account__avatar-overlay-base'>
<div
className='account__avatar'
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
>
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
</div>
</div>
<div className='account__avatar-overlay-overlay'>
<div
className='account__avatar'
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
>
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
</div>
</div>
</div>
);
};
export default AvatarOverlay;

View file

@ -32,17 +32,14 @@ function ShortNumber({ value, renderer, children }) {
const shortNumber = toShortNumber(value);
const [, division] = shortNumber;
// eslint-disable-next-line eqeqeq
if (children != null && renderer != null) {
console.warn('Both renderer prop and renderer as a child provided. This is a mistake and you really should fix that. Only renderer passed as a child will be used.');
}
// eslint-disable-next-line eqeqeq
const customRenderer = children != null ? children : renderer;
const displayNumber = <ShortNumberCounter value={shortNumber} />;
// eslint-disable-next-line eqeqeq
return customRenderer != null
? customRenderer(displayNumber, pluralReady(value, division))
: displayNumber;

View file

@ -131,4 +131,4 @@ class FilterModal extends ImmutablePureComponent {
}
export default connect(injectIntl(FilterModal));
export default connect()(injectIntl(FilterModal));

View file

@ -60,7 +60,6 @@ export function toShortNumber(sourceNumber) {
* // => 1790
*/
export function pluralReady(sourceNumber, division) {
// eslint-disable-next-line eqeqeq
if (division == null || division < DECIMAL_UNITS.HUNDRED) {
return sourceNumber;
}

View file

@ -14,7 +14,7 @@ class NotificationMailer < ApplicationMailer
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: @me.user.email, subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.mention.subject', name: @status.account.acct)
end
end
@ -25,7 +25,7 @@ class NotificationMailer < ApplicationMailer
return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow.subject', name: @account.acct)
end
end
@ -38,7 +38,7 @@ class NotificationMailer < ApplicationMailer
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: @me.user.email, subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.favourite.subject', name: @account.acct)
end
end
@ -51,7 +51,7 @@ class NotificationMailer < ApplicationMailer
locale_for_account(@me) do
thread_by_conversation(@status.conversation)
mail to: @me.user.email, subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.reblog.subject', name: @account.acct)
end
end
@ -62,7 +62,7 @@ class NotificationMailer < ApplicationMailer
return unless @me.user.functional?
locale_for_account(@me) do
mail to: @me.user.email, subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
mail to: email_address_with_name(@me.user.email, @me.user.account.username), subject: I18n.t('notification_mailer.follow_request.subject', name: @account.acct)
end
end

View file

@ -55,7 +55,7 @@ class AccountFilter
when 'by_domain'
Account.where(domain: value.to_s.strip)
when 'username'
Account.matches_username(value.to_s.strip)
Account.matches_username(value.to_s.strip.delete_prefix('@'))
when 'display_name'
Account.matches_display_name(value.to_s.strip)
when 'email'

View file

@ -18,6 +18,7 @@
#
class PreviewCardProvider < ApplicationRecord
include Paginable
include DomainNormalizable
include Attachmentable

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class REST::Admin::Trends::LinkSerializer < REST::Trends::LinkSerializer
attributes :id, :requires_review
def requires_review
object.requires_review?
end
end

View file

@ -0,0 +1,10 @@
# frozen_string_literal: true
class REST::Admin::Trends::Links::PreviewCardProviderSerializer < ActiveModel::Serializer
attributes :id, :domain, :trendable, :reviewed_at,
:requested_review_at, :requires_review
def requires_review
object.requires_review?
end
end

View file

@ -0,0 +1,9 @@
# frozen_string_literal: true
class REST::Admin::Trends::StatusSerializer < REST::StatusSerializer
attributes :requires_review
def requires_review
object.requires_review?
end
end

View file

@ -7,6 +7,7 @@ class NotifyService < BaseService
admin.report
admin.sign_up
update
poll
).freeze
def call(recipient, type, activity)