diff --git a/app/controllers/antennas_controller.rb b/app/controllers/antennas_controller.rb index b123d82e9c..5e51e9a68a 100644 --- a/app/controllers/antennas_controller.rb +++ b/app/controllers/antennas_controller.rb @@ -5,7 +5,6 @@ class AntennasController < ApplicationController before_action :authenticate_user! before_action :set_antenna, only: [:edit, :update, :destroy] - before_action :set_lists, only: [:new, :edit] before_action :set_body_classes before_action :set_cache_headers @@ -54,16 +53,12 @@ class AntennasController < ApplicationController @antenna = current_account.antennas.find(params[:id]) end - def set_lists - @lists = current_account.owned_lists - end - def resource_params - params.require(:antenna).permit(:title, :list, :available, :insert_feeds, :stl, :expires_in, :with_media_only, :ignore_reblog, :keywords_raw, :exclude_keywords_raw, :domains_raw, :exclude_domains_raw, :accounts_raw, :exclude_accounts_raw, :tags_raw, :exclude_tags_raw) + params.require(:antenna).permit(:title, :available, :expires_in) end def thin_resource_params - params.require(:antenna).permit(:title, :list) + params.require(:antenna).permit(:title) end def set_body_classes diff --git a/app/controllers/api/v1/accounts/statuses_controller.rb b/app/controllers/api/v1/accounts/statuses_controller.rb index 51f541bd23..7004dbb530 100644 --- a/app/controllers/api/v1/accounts/statuses_controller.rb +++ b/app/controllers/api/v1/accounts/statuses_controller.rb @@ -9,7 +9,9 @@ class Api::V1::Accounts::StatusesController < Api::BaseController def index cache_if_unauthenticated! @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/bookmarks_controller.rb b/app/controllers/api/v1/bookmarks_controller.rb index 498eb16f44..0985366964 100644 --- a/app/controllers/api/v1/bookmarks_controller.rb +++ b/app/controllers/api/v1/bookmarks_controller.rb @@ -7,7 +7,9 @@ class Api::V1::BookmarksController < Api::BaseController def index @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/emoji_reactions_controller.rb b/app/controllers/api/v1/emoji_reactions_controller.rb index db32962266..43b39921db 100644 --- a/app/controllers/api/v1/emoji_reactions_controller.rb +++ b/app/controllers/api/v1/emoji_reactions_controller.rb @@ -7,7 +7,9 @@ class Api::V1::EmojiReactionsController < Api::BaseController def index @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/favourites_controller.rb b/app/controllers/api/v1/favourites_controller.rb index faf1bda96a..984f3d3051 100644 --- a/app/controllers/api/v1/favourites_controller.rb +++ b/app/controllers/api/v1/favourites_controller.rb @@ -7,7 +7,9 @@ class Api::V1::FavouritesController < Api::BaseController def index @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb index 70f04df209..b0ad6754ea 100644 --- a/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb +++ b/app/controllers/api/v1/statuses/referred_by_statuses_controller.rb @@ -9,7 +9,9 @@ class Api::V1::Statuses::ReferredByStatusesController < Api::BaseController def index @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/timelines/home_controller.rb b/app/controllers/api/v1/timelines/home_controller.rb index 83b8cb4c66..b7c78e8734 100644 --- a/app/controllers/api/v1/timelines/home_controller.rb +++ b/app/controllers/api/v1/timelines/home_controller.rb @@ -9,11 +9,13 @@ class Api::V1::Timelines::HomeController < Api::BaseController with_read_replica do @statuses = load_statuses @relationships = StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + @emoji_reactions = EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end render json: @statuses, each_serializer: REST::StatusSerializer, relationships: @relationships, + emoji_reaction_permitted_account_ids: @emoji_reactions, status: account_home_feed.regenerating? ? 206 : 200 end diff --git a/app/controllers/api/v1/timelines/public_controller.rb b/app/controllers/api/v1/timelines/public_controller.rb index 5bbd92b9ee..9c320e688e 100644 --- a/app/controllers/api/v1/timelines/public_controller.rb +++ b/app/controllers/api/v1/timelines/public_controller.rb @@ -7,7 +7,9 @@ class Api::V1::Timelines::PublicController < Api::BaseController def show cache_if_unauthenticated! @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/controllers/api/v1/timelines/tag_controller.rb b/app/controllers/api/v1/timelines/tag_controller.rb index a79d65c124..1de00f50c8 100644 --- a/app/controllers/api/v1/timelines/tag_controller.rb +++ b/app/controllers/api/v1/timelines/tag_controller.rb @@ -8,7 +8,9 @@ class Api::V1::Timelines::TagController < Api::BaseController def show cache_if_unauthenticated! @statuses = load_statuses - render json: @statuses, each_serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id) + render json: @statuses, each_serializer: REST::StatusSerializer, + relationships: StatusRelationshipsPresenter.new(@statuses, current_user&.account_id), + emoji_reaction_permitted_account_ids: EmojiReactionAccountsPresenter.new(@statuses, current_user&.account_id) end private diff --git a/app/javascript/mastodon/components/status.jsx b/app/javascript/mastodon/components/status.jsx index 257e90d1c1..0f18e4be9e 100644 --- a/app/javascript/mastodon/components/status.jsx +++ b/app/javascript/mastodon/components/status.jsx @@ -585,6 +585,10 @@ class Status extends ImmutablePureComponent { const {statusContentProps, hashtagBar} = getHashtagBarForStatus(status); const expanded = !status.get('hidden') + const withLimited = status.get('visibility_ex') === 'limited' && status.get('limited_scope') ? : null; + const withReference = status.get('status_references_count') > 0 ? : null; + const withExpiration = status.get('expires_at') ? : null; + return (
@@ -596,6 +600,9 @@ class Status extends ImmutablePureComponent { {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
+ {withReference} + {withExpiration} + {withLimited} {status.get('edited_at') && *} diff --git a/app/javascript/mastodon/features/reaction_deck/index.jsx b/app/javascript/mastodon/features/reaction_deck/index.jsx index 7492d3cfb7..ebc2985abc 100644 --- a/app/javascript/mastodon/features/reaction_deck/index.jsx +++ b/app/javascript/mastodon/features/reaction_deck/index.jsx @@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd'; +import { DragDropContext, Droppable, Draggable } from '@hello-pangea/dnd'; import { updateReactionDeck } from 'mastodon/actions/reaction_deck'; import Button from 'mastodon/components/button'; diff --git a/app/lib/status_cache_hydrator.rb b/app/lib/status_cache_hydrator.rb index f6448b15ac..e4a00fa768 100644 --- a/app/lib/status_cache_hydrator.rb +++ b/app/lib/status_cache_hydrator.rb @@ -35,7 +35,7 @@ class StatusCacheHydrator payload[:reblog][:bookmarked] = Bookmark.where(account_id: account_id, status_id: @status.reblog_of_id).exists? payload[:reblog][:pinned] = StatusPin.where(account_id: account_id, status_id: @status.reblog_of_id).exists? if @status.reblog.account_id == account_id payload[:reblog][:filtered] = payload[:filtered] - payload[:reblog][:emoji_reactions] = @status.emoji_reactions_grouped_by_name(account) + payload[:reblog][:emoji_reactions] = @status.reblog.emoji_reactions_grouped_by_name(account) if payload[:reblog][:poll] if @status.reblog.account_id == account_id diff --git a/app/models/antenna.rb b/app/models/antenna.rb index c14892e138..a35fa6dd17 100644 --- a/app/models/antenna.rb +++ b/app/models/antenna.rb @@ -92,142 +92,6 @@ class Antenna < ApplicationRecord context end - def list=(list_id) - list_id = list_id.to_i if list_id.is_a?(String) - if list_id.is_a?(Numeric) - self[:list_id] = list_id - else - self[:list] = list_id - end - end - - def keywords_raw - return '' if keywords.blank? - - keywords.join("\n") - end - - def keywords_raw=(raw) - keywords = raw.split(/\R/).filter { |r| r.present? && r.length >= 2 }.uniq - self[:keywords] = keywords - self[:any_keywords] = keywords.none? - end - - def exclude_keywords_raw - return '' if exclude_keywords.blank? - - exclude_keywords.join("\n") - end - - def exclude_keywords_raw=(raw) - exclude_keywords = raw.split(/\R/).filter(&:present?).uniq - self[:exclude_keywords] = exclude_keywords - end - - def tags_raw - antenna_tags.where(exclude: false).map { |tag| tag.tag.name }.join("\n") - end - - def tags_raw=(raw) - return if tags_raw == raw - - tag_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('#') ? r[1..] : r }.uniq - - antenna_tags.where(exclude: false).destroy_all - Tag.find_or_create_by_names(tag_names).each do |tag| - antenna_tags.create!(tag: tag, exclude: false) - end - self[:any_tags] = tag_names.none? - end - - def exclude_tags_raw - return '' if exclude_tags.blank? - - Tag.where(id: exclude_tags).map(&:name).join("\n") - end - - def exclude_tags_raw=(raw) - return if exclude_tags_raw == raw - - tags = [] - tag_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('#') ? r[1..] : r }.uniq - Tag.find_or_create_by_names(tag_names).each do |tag| - tags << tag.id - end - self[:exclude_tags] = tags - end - - def domains_raw - antenna_domains.where(exclude: false).map(&:name).join("\n") - end - - def domains_raw=(raw) - return if domains_raw == raw - - domain_names = raw.split(/\R/).filter(&:present?).uniq - - antenna_domains.where(exclude: false).destroy_all - domain_names.each do |domain| - antenna_domains.create!(name: domain, exclude: false) - end - self[:any_domains] = domain_names.none? - end - - def exclude_domains_raw - return '' if exclude_domains.blank? - - exclude_domains.join("\n") - end - - def exclude_domains_raw=(raw) - return if exclude_domains_raw == raw - - domain_names = raw.split(/\R/).filter(&:present?).uniq - self[:exclude_domains] = domain_names - end - - def accounts_raw - antenna_accounts.where(exclude: false).map(&:account).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n") - end - - def accounts_raw=(raw) - return if accounts_raw == raw - - account_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('@') ? r[1..] : r }.uniq - - hit = false - antenna_accounts.where(exclude: false).destroy_all - account_names.each do |name| - username, domain = name.split('@') - account = Account.find_by(username: username, domain: domain) - if account.present? - antenna_accounts.create!(account: account, exclude: false) - hit = true - end - end - self[:any_accounts] = !hit - end - - def exclude_accounts_raw - return '' if exclude_accounts.blank? - - Account.where(id: exclude_accounts).map { |account| account.domain ? "@#{account.username}@#{account.domain}" : "@#{account.username}" }.join("\n") - end - - def exclude_accounts_raw=(raw) - return if exclude_accounts_raw == raw - - account_names = raw.split(/\R/).filter(&:present?).map { |r| r.start_with?('@') ? r[1..] : r }.uniq - - accounts = [] - account_names.each do |name| - username, domain = name.split('@') - account = Account.find_by(username: username, domain: domain) - accounts << account.id if account.present? - end - self[:exclude_accounts] = accounts - end - private def validate_limit diff --git a/app/models/public_feed.rb b/app/models/public_feed.rb index 1ed68e45e9..a641e77039 100644 --- a/app/models/public_feed.rb +++ b/app/models/public_feed.rb @@ -28,7 +28,8 @@ class PublicFeed scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? scope.merge!(language_scope) if account&.chosen_languages.present? - scope.merge!(anonymous_scope) unless account? + # scope.merge!(anonymous_scope) unless account? + scope = to_anonymous_scope(scope) unless account? scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end @@ -105,6 +106,10 @@ class PublicFeed local_only? ? Status.where(visibility: [:public, :public_unlisted]) : Status.where(visibility: :public) end + def to_anonymous_scope(scope) + scope.where.not(visibility: :login) + end + def account_filters_scope Status.not_excluded_by_account(account).tap do |scope| scope.merge!(Status.not_domain_blocked_by_account(account)) unless local_only? diff --git a/app/models/status.rb b/app/models/status.rb index c6c05e95e7..d4a28220d1 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -173,6 +173,8 @@ class Status < ApplicationRecord :tags, :preview_cards, :preloadable_poll, + :reference_objects, + :scheduled_expiration_status, account: [:account_stat, user: :role], active_mentions: { account: :account_stat }, reblog: [ @@ -183,6 +185,8 @@ class Status < ApplicationRecord :conversation, :status_stat, :preloadable_poll, + :reference_objects, + :scheduled_expiration_status, account: [:account_stat, user: :role], active_mentions: { account: :account_stat }, ], @@ -353,21 +357,30 @@ class Status < ApplicationRecord return [] if account.present? && !self.account.show_emoji_reaction?(account) return [] if account.nil? && !options[:force] && self.account.emoji_reaction_policy != :allow + permitted_account_ids = options[:permitted_account_ids] + (Oj.load(status_stat&.emoji_reactions || '', mode: :strict) || []).tap do |emoji_reactions| if account.present? - remove_emoji_reactions = [] + public_emoji_reactions = [] + emoji_reactions.each do |emoji_reaction| emoji_reaction['me'] = emoji_reaction['account_ids'].include?(account.id.to_s) emoji_reaction['account_ids'] -= account.excluded_from_timeline_account_ids.map(&:to_s) - accounts = Account.where(id: emoji_reaction['account_ids'], silenced_at: nil, suspended_at: nil) - accounts = accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.excluded_from_timeline_domains.size.positive? - emoji_reaction['account_ids'] = accounts.pluck(:id).map(&:to_s) + accounts = [] + if permitted_account_ids + emoji_reaction['account_ids'] = emoji_reaction['account_ids'] & permitted_account_ids.map(&:to_s) + else + accounts = Account.where(id: emoji_reaction['account_ids'], silenced_at: nil, suspended_at: nil) + accounts = accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.excluded_from_timeline_domains.size.positive? + emoji_reaction['account_ids'] = accounts.pluck(:id).map(&:to_s) + end emoji_reaction['count'] = emoji_reaction['account_ids'].size - remove_emoji_reactions << emoji_reaction if emoji_reaction['count'] <= 0 + public_emoji_reactions << emoji_reaction if (emoji_reaction['count']).positive? end - emoji_reactions - remove_emoji_reactions + + public_emoji_reactions end end end @@ -459,6 +472,11 @@ class Status < ApplicationRecord EmojiReaction.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |e, h| h[e.status_id] = true } end + def emoji_reaction_allows_map(status_ids, account_id) + my_account = Account.find_by(id: account_id) + Status.where(id: status_ids).pluck(:account_id).uniq.index_with { |a| Account.find_by(id: a).show_emoji_reaction?(my_account) } + end + def reload_stale_associations!(cached_items) account_ids = [] diff --git a/app/models/status_reference.rb b/app/models/status_reference.rb index 2ed5b2aee9..62be6df3b1 100644 --- a/app/models/status_reference.rb +++ b/app/models/status_reference.rb @@ -18,8 +18,16 @@ class StatusReference < ApplicationRecord has_one :notification, as: :activity, dependent: :destroy validate :validate_status_visibilities + after_commit :reset_parent_cache + + private def validate_status_visibilities raise Mastodon::ValidationError, I18n.t('status_references.errors.invalid_status_visibilities') if [:public, :public_unlisted, :unlisted, :login].exclude?(target_status.visibility.to_sym) end + + def reset_parent_cache + Rails.cache.delete("statuses/#{status_id}") + Rails.cache.delete("statuses/#{target_status_id}") + end end diff --git a/app/models/tag_feed.rb b/app/models/tag_feed.rb index 14f4afe737..9c679a0249 100644 --- a/app/models/tag_feed.rb +++ b/app/models/tag_feed.rb @@ -32,7 +32,8 @@ class TagFeed < PublicFeed scope.merge!(remote_only_scope) if remote_only? || hide_local_users? scope.merge!(account_filters_scope) if account? scope.merge!(media_only_scope) if media_only? - scope.merge!(anonymous_scope) unless account? + # scope.merge!(anonymous_scope) unless account? + scope = to_anonymous_scope(scope) unless account? scope.cache_ids.to_a_paginated_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id) end diff --git a/app/presenters/emoji_reaction_accounts_presenter.rb b/app/presenters/emoji_reaction_accounts_presenter.rb new file mode 100644 index 0000000000..1b683f3366 --- /dev/null +++ b/app/presenters/emoji_reaction_accounts_presenter.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class EmojiReactionAccountsPresenter + attr_reader :permitted_account_ids + + def initialize(statuses, current_account_id = nil, **_options) + @current_account_id = current_account_id + + statuses = statuses.compact + status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact + emoji_reactions = EmojiReaction.where(status_id: status_ids) + account_ids = emoji_reactions.pluck(:account_id).uniq + + permitted_accounts = Account.where(id: account_ids, silenced_at: nil, suspended_at: nil) + if current_account_id.present? + account = Account.find(current_account_id) + permitted_accounts = permitted_accounts.where('domain IS NULL OR domain NOT IN (?)', account.excluded_from_timeline_domains) if account.present? && account.excluded_from_timeline_domains.size.positive? + end + + @permitted_account_ids = permitted_accounts.pluck(:id) + end +end diff --git a/app/presenters/status_relationships_presenter.rb b/app/presenters/status_relationships_presenter.rb index 3a5f0e1589..9e55742403 100644 --- a/app/presenters/status_relationships_presenter.rb +++ b/app/presenters/status_relationships_presenter.rb @@ -4,7 +4,7 @@ class StatusRelationshipsPresenter PINNABLE_VISIBILITIES = %w(public public_unlisted unlisted login private).freeze attr_reader :reblogs_map, :favourites_map, :mutes_map, :pins_map, - :bookmarks_map, :filters_map, :emoji_reactions_map, :attributes_map + :bookmarks_map, :filters_map, :emoji_reactions_map, :attributes_map, :emoji_reaction_allows_map def initialize(statuses, current_account_id = nil, **options) @current_account_id = current_account_id @@ -17,6 +17,7 @@ class StatusRelationshipsPresenter @pins_map = {} @filters_map = {} @emoji_reactions_map = {} + @emoji_reaction_allows_map = nil else statuses = statuses.compact status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.uniq.compact @@ -30,6 +31,7 @@ class StatusRelationshipsPresenter @mutes_map = Status.mutes_map(conversation_ids, current_account_id).merge(options[:mutes_map] || {}) @pins_map = Status.pins_map(pinnable_status_ids, current_account_id).merge(options[:pins_map] || {}) @emoji_reactions_map = Status.emoji_reactions_map(status_ids, current_account_id).merge(options[:emoji_reactions_map] || {}) + @emoji_reaction_allows_map = Status.emoji_reaction_allows_map(status_ids, current_account_id).merge(options[:emoji_reaction_allows_map] || {}) @attributes_map = options[:attributes_map] || {} end end diff --git a/app/serializers/rest/status_serializer.rb b/app/serializers/rest/status_serializer.rb index e53fb535bc..48aecb2994 100644 --- a/app/serializers/rest/status_serializer.rb +++ b/app/serializers/rest/status_serializer.rb @@ -15,6 +15,7 @@ class REST::StatusSerializer < ActiveModel::Serializer attribute :bookmarked, if: :current_user? attribute :pinned, if: :pinnable? attribute :reactions, if: :reactions? + attribute :expires_at, if: :will_expire? has_many :filtered, serializer: REST::FilterResultSerializer, if: :current_user? attribute :content, unless: :source_requested? @@ -122,7 +123,7 @@ class REST::StatusSerializer < ActiveModel::Serializer end def emoji_reactions - object.emoji_reactions_grouped_by_name(current_user&.account) + show_emoji_reaction? ? object.emoji_reactions_grouped_by_name(current_user&.account, permitted_account_ids: emoji_reaction_permitted_account_ids) : [] end def emoji_reactions_count @@ -131,7 +132,17 @@ class REST::StatusSerializer < ActiveModel::Serializer object.account.emoji_reaction_policy == :allow ? object.emoji_reactions_count : 0 else - object.account.show_emoji_reaction?(current_user.account) ? object.emoji_reactions_count : 0 + show_emoji_reaction? ? object.emoji_reactions_count : 0 + end + end + + def show_emoji_reaction? + if relationships + return true if relationships.emoji_reaction_allows_map.nil? + + relationships.emoji_reaction_allows_map[object.account_id] || false + else + object.account.show_emoji_reaction?(current_user.account) end end @@ -205,12 +216,24 @@ class REST::StatusSerializer < ActiveModel::Serializer object.active_mentions.to_a.sort_by(&:id) end + def will_expire? + object.scheduled_expiration_status.present? + end + + def expires_at + object.scheduled_expiration_status.scheduled_at + end + private def relationships instance_options && instance_options[:relationships] end + def emoji_reaction_permitted_account_ids + current_user.present? && instance_options && instance_options[:emoji_reaction_permitted_account_ids]&.permitted_account_ids + end + class ApplicationSerializer < ActiveModel::Serializer attributes :name, :website diff --git a/app/services/process_references_service.rb b/app/services/process_references_service.rb index 0dbdc43560..9c8b3813f5 100644 --- a/app/services/process_references_service.rb +++ b/app/services/process_references_service.rb @@ -5,13 +5,14 @@ class ProcessReferencesService < BaseService DOMAIN = ENV['WEB_DOMAIN'] || ENV.fetch('LOCAL_DOMAIN', nil) REFURL_EXP = /(RT|QT|BT|RN|RE)((:|;)?\s+|:|;)(#{URI::DEFAULT_PARSER.make_regexp(%w(http https))})/ + MAX_REFERENCES = 5 def call(status, reference_parameters, urls: nil) @status = status @reference_parameters = reference_parameters || [] @urls = urls || [] - old_references + @references_count = old_references.size return unless added_references.size.positive? || removed_references.size.positive? @@ -63,6 +64,9 @@ class ProcessReferencesService < BaseService statuses.each do |status| @added_objects << @status.reference_objects.new(target_status: status) status.increment_count!(:status_referred_by_count) + @references_count += 1 + + break if @references_count >= MAX_REFERENCES end end @@ -85,6 +89,7 @@ class ProcessReferencesService < BaseService @status.reference_objects.where(target_status: statuses).destroy_all statuses.each do |status| status.decrement_count!(:status_referred_by_count) + @references_count -= 1 end end end diff --git a/app/views/antennas/_antenna_fields.html.haml b/app/views/antennas/_antenna_fields.html.haml index ac742d9d38..998aa5fdeb 100644 --- a/app/views/antennas/_antenna_fields.html.haml +++ b/app/views/antennas/_antenna_fields.html.haml @@ -8,57 +8,4 @@ = f.input :expires_in, wrapper: :with_label, collection: [30.minutes, 1.hour, 6.hours, 12.hours, 1.day, 1.week].map(&:to_i), label_method: lambda { |i| I18n.t("invites.expires_in.#{i}") }, include_blank: I18n.t('invites.expires_in_prompt') .fields-row - .fields-group.fields-row__column.fields-row__column-6 - = f.input :list, collection: lists, wrapper: :with_label, label_method: lambda { |list| list.title }, label: t('antennas.edit.list'), selected: f.object.list&.id, hint: false, include_blank: '[Insert to Home]' - .fields-group.fields-row__column.fields-row__column-6 - = f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false - -.fields-row - = f.input :insert_feeds, wrapper: :with_label, label: t('antennas.edit.insert_feeds') -.fields-row - = f.input :stl, wrapper: :with_label, label: t('antennas.edit.stl'), hint: t('antennas.edit.stl_hint') - -%hr.spacer/ -%p.hint= t 'antennas.edit.hint' -%hr.spacer/ - -%h4= t('antennas.contexts.domain') -%p.hint= t 'antennas.edit.domains_hint' - -.fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.domains_raw') - .fields-row__column.fields-row__column-6.fields-group - = f.input :exclude_domains_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_domains_raw') - -%h4= t('antennas.contexts.account') -%p.hint= t 'antennas.edit.accounts_hint' - -.fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.accounts_raw') - .fields-row__column.fields-row__column-6.fields-group - = f.input :exclude_accounts_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_accounts_raw') - -%h4= t('antennas.contexts.tag') - -.fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.tags_raw') - .fields-row__column.fields-row__column-6.fields-group - = f.input :exclude_tags_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_tags_raw') - -%h4= t('antennas.contexts.keyword') -%p.hint= t 'antennas.edit.keywords_hint' - -.fields-row - .fields-row__column.fields-row__column-6.fields-group - = f.input :keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.keywords_raw') - .fields-row__column.fields-row__column-6.fields-group - = f.input :exclude_keywords_raw, wrapper: :with_label, as: :text, input_html: { rows: 5 }, label: t('antennas.edit.exclude_keywords_raw') - -%hr.spacer/ -.fields-group - = f.input :with_media_only, wrapper: :with_label, label: t('antennas.edit.with_media_only'), hint: false -.fields-group - = f.input :ignore_reblog, wrapper: :with_label, label: t('antennas.edit.ignore_reblog'), hint: false + = f.input :available, wrapper: :with_label, label: t('antennas.edit.available'), hint: false diff --git a/app/workers/activitypub/fetch_instance_info_worker.rb b/app/workers/activitypub/fetch_instance_info_worker.rb index faf7401fc9..57cbd97d10 100644 --- a/app/workers/activitypub/fetch_instance_info_worker.rb +++ b/app/workers/activitypub/fetch_instance_info_worker.rb @@ -9,7 +9,6 @@ class ActivityPub::FetchInstanceInfoWorker sidekiq_options queue: 'push', retry: 2 class Error < StandardError; end - class GoneError < Error; end class RequestError < Error; end class DeadError < Error; end @@ -68,9 +67,7 @@ class ActivityPub::FetchInstanceInfoWorker raise Mastodon::UnexpectedResponseError, response unless response_successful?(response) || response_error_unsalvageable?(response) body_to_json(response.body_with_limit) - elsif response.code == 410 - raise ActivityPub::FetchInstanceInfoWorker::GoneError, "#{@instance.domain} is gone from the server" - elsif response.code == 404 + elsif [400, 401, 403, 404, 410].include?(response.code) raise ActivityPub::FetchInstanceInfoWorker::DeadError, "Request for #{@instance.domain} returned HTTP #{response.code}" else raise ActivityPub::FetchInstanceInfoWorker::RequestError, "Request for #{@instance.domain} returned HTTP #{response.code}" diff --git a/lib/mastodon/version.rb b/lib/mastodon/version.rb index c00ed740a2..92179d9b8f 100644 --- a/lib/mastodon/version.rb +++ b/lib/mastodon/version.rb @@ -5,7 +5,7 @@ module Mastodon module_function def kmyblue_major - 3 + 4 end def kmyblue_minor diff --git a/package.json b/package.json index 8f95f09fa4..5de05c53b6 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@formatjs/intl-pluralrules": "^5.2.2", "@gamestdio/websocket": "^0.3.2", "@github/webauthn-json": "^2.1.1", + "@hello-pangea/dnd": "^16.3.0", "@material-design-icons/svg": "^0.14.10", "@rails/ujs": "^7.0.6", "@reduxjs/toolkit": "^1.9.5", @@ -100,7 +101,6 @@ "prop-types": "^15.8.1", "punycode": "^2.3.0", "react": "^18.2.0", - "react-beautiful-dnd": "^13.1.1", "react-dom": "^18.2.0", "react-helmet": "^6.1.0", "react-hotkeys": "^1.1.4", diff --git a/spec/controllers/api/v1/circles/accounts_controller_spec.rb b/spec/controllers/api/v1/circles/accounts_controller_spec.rb new file mode 100644 index 0000000000..bddd8aa211 --- /dev/null +++ b/spec/controllers/api/v1/circles/accounts_controller_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::Circles::AccountsController do + render_views + + let(:user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: scopes) } + let(:circle) { Fabricate(:circle, account: user.account) } + let(:follow) { Fabricate(:follow, target_account: user.account) } + + before do + circle.accounts << follow.account + allow(controller).to receive(:doorkeeper_token) { token } + end + + describe 'GET #index' do + let(:scopes) { 'read:lists' } + + it 'returns http success' do + get :show, params: { circle_id: circle.id } + + expect(response).to have_http_status(200) + end + end + + describe 'POST #create' do + let(:scopes) { 'write:lists' } + let(:bob) { Fabricate(:account, username: 'bob') } + + context 'when the added account is followed' do + before do + bob.follow!(user.account) + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'adds account to the circle' do + expect(circle.accounts.include?(bob)).to be true + end + end + + context 'when the added account has been sent a follow request' do + before do + bob.follow_requests.create!(target_account: user.account) + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(404) + end + + it 'adds account to the circle' do + expect(circle.accounts.include?(bob)).to be false + end + end + + context 'when the added account is not followed' do + before do + post :create, params: { circle_id: circle.id, account_ids: [bob.id] } + end + + it 'returns http not found' do + expect(response).to have_http_status(404) + end + + it 'does not add the account to the circle' do + expect(circle.accounts.include?(bob)).to be false + end + end + end + + describe 'DELETE #destroy' do + let(:scopes) { 'write:lists' } + + before do + delete :destroy, params: { circle_id: circle.id, account_ids: [circle.accounts.first.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(200) + end + + it 'removes account from the circle' do + expect(circle.accounts.count).to eq 0 + end + end +end diff --git a/spec/controllers/api/v1/circles_controller_spec.rb b/spec/controllers/api/v1/circles_controller_spec.rb new file mode 100644 index 0000000000..1aff55e65b --- /dev/null +++ b/spec/controllers/api/v1/circles_controller_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::CirclesController do + render_views + + let(:user) { Fabricate(:user) } + let(:circle) { Fabricate(:circle, account: user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + context 'with a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') } + + describe 'GET #show' do + it 'returns http success' do + get :show, params: { id: circle.id } + expect(response).to have_http_status(200) + end + end + end + + context 'with the wrong user context' do + let(:other_user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') } + + describe 'GET #show' do + it 'returns http not found' do + get :show, params: { id: circle.id } + expect(response).to have_http_status(404) + end + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') } + + describe 'GET #show' do + it 'returns http unprocessable entity' do + get :show, params: { id: circle.id } + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end diff --git a/spec/controllers/api/v1/lists_controller_spec.rb b/spec/controllers/api/v1/lists_controller_spec.rb new file mode 100644 index 0000000000..b54f3f70fa --- /dev/null +++ b/spec/controllers/api/v1/lists_controller_spec.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require 'rails_helper' + +describe Api::V1::ListsController do + render_views + + let(:user) { Fabricate(:user) } + let(:list) { Fabricate(:list, account: user.account) } + + before do + allow(controller).to receive(:doorkeeper_token) { token } + end + + context 'with a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: user.id, scopes: 'read:lists') } + + describe 'GET #show' do + it 'returns http success' do + get :show, params: { id: list.id } + expect(response).to have_http_status(200) + end + end + end + + context 'with the wrong user context' do + let(:other_user) { Fabricate(:user) } + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: other_user.id, scopes: 'read') } + + describe 'GET #show' do + it 'returns http not found' do + get :show, params: { id: list.id } + expect(response).to have_http_status(404) + end + end + end + + context 'without a user context' do + let(:token) { Fabricate(:accessible_access_token, resource_owner_id: nil, scopes: 'read') } + + describe 'GET #show' do + it 'returns http unprocessable entity' do + get :show, params: { id: list.id } + + expect(response).to have_http_status(422) + expect(response.headers['Link']).to be_nil + end + end + end +end diff --git a/spec/models/tag_feed_spec.rb b/spec/models/tag_feed_spec.rb index 6f5e1eb307..270797ccd8 100644 --- a/spec/models/tag_feed_spec.rb +++ b/spec/models/tag_feed_spec.rb @@ -66,5 +66,47 @@ describe TagFeed, type: :service do results = described_class.new(tag_cats, nil).get(20) expect(results).to include(status) end + + it 'public_unlisted post returns' do + status_tagged_with_cats.update(visibility: :public_unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'unlisted post not returns' do + status_tagged_with_cats.update(visibility: :unlisted) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'unlisted post not returns with account' do + status_tagged_with_cats.update(visibility: :unlisted) + results = described_class.new(tag_cats, account).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'unlisted/public_searchability post returns' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'unlisted/public_searchability post returns with account' do + status_tagged_with_cats.update(visibility: :unlisted, searchability: :public) + results = described_class.new(tag_cats, account).get(20) + expect(results).to include status_tagged_with_cats + end + + it 'private post not returns' do + status_tagged_with_cats.update(visibility: :private, searchability: :public) + results = described_class.new(tag_cats, nil).get(20) + expect(results).to_not include status_tagged_with_cats + end + + it 'private post not returns with account' do + status_tagged_with_cats.update(visibility: :private, searchability: :public) + results = described_class.new(tag_cats, account).get(20) + expect(results).to_not include status_tagged_with_cats + end end end diff --git a/spec/services/delivery_antenna_service_spec.rb b/spec/services/delivery_antenna_service_spec.rb index aa63aed74c..26c23eb930 100644 --- a/spec/services/delivery_antenna_service_spec.rb +++ b/spec/services/delivery_antenna_service_spec.rb @@ -266,8 +266,8 @@ RSpec.describe DeliveryAntennaService, type: :service do end context 'when multiple antennas insert list with keyword' do - let!(:antenna) { antenna_with_keyword(bob, 'body', insert_feeds: true, list: list(bob).id) } - let!(:empty_antenna) { antenna_with_keyword(tom, 'body', insert_feeds: true, list: list(tom).id) } + let!(:antenna) { antenna_with_keyword(bob, 'body', insert_feeds: true, list: list(bob)) } + let!(:empty_antenna) { antenna_with_keyword(tom, 'body', insert_feeds: true, list: list(tom)) } it 'detecting antenna' do expect(antenna_feed_of(antenna)).to include status.id diff --git a/spec/services/fan_out_on_write_service_spec.rb b/spec/services/fan_out_on_write_service_spec.rb index c15253c879..65db23214a 100644 --- a/spec/services/fan_out_on_write_service_spec.rb +++ b/spec/services/fan_out_on_write_service_spec.rb @@ -7,9 +7,10 @@ RSpec.describe FanOutOnWriteService, type: :service do let(:last_active_at) { Time.now.utc } let(:searchability) { 'public' } + let(:dissubscribable) { false } let(:status) { Fabricate(:status, account: alice, visibility: visibility, searchability: searchability, text: 'Hello @bob #hoge') } - let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at).account } + let!(:alice) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { dissubscribable: dissubscribable }).account } let!(:bob) { Fabricate(:user, current_sign_in_at: last_active_at, account_attributes: { username: 'bob' }).account } let!(:tom) { Fabricate(:user, current_sign_in_at: last_active_at).account } let!(:ohagi) { Fabricate(:user, current_sign_in_at: last_active_at).account } @@ -100,6 +101,14 @@ RSpec.describe FanOutOnWriteService, type: :service do expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is not added to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + end end context 'with STL antenna' do @@ -110,6 +119,14 @@ RSpec.describe FanOutOnWriteService, type: :service do expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end end context 'with LTL antenna' do @@ -120,6 +137,14 @@ RSpec.describe FanOutOnWriteService, type: :service do expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(empty_antenna)).to_not include status.id end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end end end @@ -238,6 +263,89 @@ RSpec.describe FanOutOnWriteService, type: :service do end end + context 'when status is public_unlisted' do + let(:visibility) { 'public_unlisted' } + + it 'is added to the home feed of its author' do + expect(home_feed_of(alice)).to include status.id + end + + it 'is added to the home feed of a follower' do + expect(home_feed_of(bob)).to include status.id + expect(home_feed_of(tom)).to include status.id + end + + it 'is broadcast publicly' do + expect(redis).to have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to have_received(:publish).with('timeline:public:local', anything) + expect(redis).to_not have_received(:publish).with('timeline:public', anything) + end + + context 'with list' do + let!(:list) { list_with_account(bob, alice) } + let!(:empty_list) { list_with_account(ohagi, bob) } + + it 'is added to the list feed of list follower' do + expect(list_feed_of(list)).to include status.id + expect(list_feed_of(empty_list)).to_not include status.id + end + end + + context 'with antenna' do + let!(:antenna) { antenna_with_account(bob, alice) } + let!(:empty_antenna) { antenna_with_account(tom, bob) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is not added to the antenna feed' do + expect(antenna_feed_of(antenna)).to_not include status.id + end + end + end + + context 'with STL antenna' do + let!(:antenna) { antenna_with_options(bob, stl: true) } + let!(:empty_antenna) { antenna_with_options(tom) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end + + context 'with LTL antenna' do + let!(:antenna) { antenna_with_options(bob, ltl: true) } + let!(:empty_antenna) { antenna_with_options(tom) } + + it 'is added to the antenna feed of antenna follower' do + expect(antenna_feed_of(antenna)).to include status.id + expect(antenna_feed_of(empty_antenna)).to_not include status.id + end + + context 'when dissubscribable is true' do + let(:dissubscribable) { true } + + it 'is added to the antenna feed' do + expect(antenna_feed_of(antenna)).to include status.id + end + end + end + end + context 'when status is unlisted' do let(:visibility) { 'unlisted' } @@ -255,6 +363,15 @@ RSpec.describe FanOutOnWriteService, type: :service do expect(redis).to_not have_received(:publish).with('timeline:public', anything) end + context 'with searchability private' do + let(:searchability) { 'private' } + + it 'is not broadcast to the hashtag stream' do + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge', anything) + expect(redis).to_not have_received(:publish).with('timeline:hashtag:hoge:local', anything) + end + end + context 'with list' do let!(:list) { list_with_account(bob, alice) } let!(:empty_list) { list_with_account(ohagi, bob) } diff --git a/yarn.lock b/yarn.lock index 95bea8c933..0c5eddba7c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1098,7 +1098,7 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.15.4", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.13", "@babel/runtime@^7.20.7", "@babel/runtime@^7.22.3", "@babel/runtime@^7.22.5", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.15.tgz#38f46494ccf6cf020bd4eed7124b425e83e523b8" integrity sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA== @@ -1458,6 +1458,19 @@ resolved "https://registry.yarnpkg.com/@github/webauthn-json/-/webauthn-json-2.1.1.tgz#648e63fc28050917d2882cc2b27817a88cb420fc" integrity sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ== +"@hello-pangea/dnd@^16.3.0": + version "16.3.0" + resolved "https://registry.yarnpkg.com/@hello-pangea/dnd/-/dnd-16.3.0.tgz#3776212f812df4e8e69c42831ec8ab7ff3a087d6" + integrity sha512-RYQ/K8shtJoyNPvFWz0gfXIK7HF3P3mL9UZFGMuHB0ljRSXVgMjVFI/FxcZmakMzw6tO7NflWLriwTNBow/4vw== + dependencies: + "@babel/runtime" "^7.22.5" + css-box-model "^1.2.1" + memoize-one "^6.0.0" + raf-schd "^4.0.3" + react-redux "^8.1.1" + redux "^4.2.1" + use-memo-one "^1.1.3" + "@humanwhocodes/config-array@^0.11.11": version "0.11.11" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844" @@ -2190,7 +2203,7 @@ resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64" integrity sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA== -"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA== @@ -2403,16 +2416,6 @@ dependencies: react-overlays "*" -"@types/react-redux@^7.1.20": - version "7.1.25" - resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.1.25.tgz#de841631205b24f9dfb4967dd4a7901e048f9a88" - integrity sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg== - dependencies: - "@types/hoist-non-react-statics" "^3.3.0" - "@types/react" "*" - hoist-non-react-statics "^3.3.0" - redux "^4.0.0" - "@types/react-router-dom@^5.3.3": version "5.3.3" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.3.tgz#e9d6b4a66fcdbd651a5f106c2656a30088cc1e83" @@ -4456,7 +4459,7 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== -css-box-model@^1.2.0: +css-box-model@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" integrity sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw== @@ -8368,11 +8371,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -memoize-one@^5.1.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" - integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== - memoize-one@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045" @@ -9924,7 +9922,7 @@ quick-lru@^5.1.1: resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== -raf-schd@^4.0.2: +raf-schd@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== @@ -9966,19 +9964,6 @@ raw-body@2.5.1: iconv-lite "0.4.24" unpipe "1.0.0" -react-beautiful-dnd@^13.1.1: - version "13.1.1" - resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" - integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ== - dependencies: - "@babel/runtime" "^7.9.2" - css-box-model "^1.2.0" - memoize-one "^5.1.1" - raf-schd "^4.0.2" - react-redux "^7.2.0" - redux "^4.0.4" - use-memo-one "^1.1.1" - react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -10060,7 +10045,7 @@ react-is@^16.13.1, react-is@^16.7.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== -react-is@^17.0.1, react-is@^17.0.2: +react-is@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== @@ -10108,19 +10093,7 @@ react-redux-loading-bar@^5.0.4: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -react-redux@^7.2.0: - version "7.2.9" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" - integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== - dependencies: - "@babel/runtime" "^7.15.4" - "@types/react-redux" "^7.1.20" - hoist-non-react-statics "^3.3.2" - loose-envify "^1.4.0" - prop-types "^15.7.2" - react-is "^17.0.2" - -react-redux@^8.0.4: +react-redux@^8.0.4, react-redux@^8.1.1: version "8.1.2" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.1.2.tgz#9076bbc6b60f746659ad6d51cb05de9c5e1e9188" integrity sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw== @@ -10378,7 +10351,7 @@ redux-thunk@^2.4.2: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.2.tgz#b9d05d11994b99f7a91ea223e8b04cf0afa5ef3b" integrity sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q== -redux@^4.0.0, redux@^4.0.4, redux@^4.2.1: +redux@^4.0.0, redux@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.1.tgz#c08f4306826c49b5e9dc901dee0452ea8fce6197" integrity sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w== @@ -12312,7 +12285,7 @@ use-latest@^1.2.1: dependencies: use-isomorphic-layout-effect "^1.1.1" -use-memo-one@^1.1.1: +use-memo-one@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/use-memo-one/-/use-memo-one-1.1.3.tgz#2fd2e43a2169eabc7496960ace8c79efef975e99" integrity sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==