Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/javascript/styles/mastodon/modal.scss`: For some reason we changed the file loading path in glitch-soc, but now upstream has completely changed how the logo is loaded. Applied upstream changes.
This commit is contained in:
		
						commit
						5e11f3a6e1
					
				|  | @ -30,7 +30,7 @@ plugins: | |||
|     channel: eslint-7 | ||||
|   rubocop: | ||||
|     enabled: true | ||||
|     channel: rubocop-1-8-1 | ||||
|     channel: rubocop-1-9-1 | ||||
|   sass-lint: | ||||
|     enabled: true | ||||
| exclude_patterns: | ||||
|  |  | |||
							
								
								
									
										8
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										8
									
								
								Gemfile
								
								
								
								
							|  | @ -17,7 +17,7 @@ gem 'makara', '~> 0.5' | |||
| gem 'pghero', '~> 2.7' | ||||
| gem 'dotenv-rails', '~> 2.7' | ||||
| 
 | ||||
| gem 'aws-sdk-s3', '~> 1.87', require: false | ||||
| gem 'aws-sdk-s3', '~> 1.88', require: false | ||||
| gem 'fog-core', '<= 2.1.0' | ||||
| gem 'fog-openstack', '~> 0.3', require: false | ||||
| gem 'paperclip', '~> 6.0' | ||||
|  | @ -27,7 +27,7 @@ gem 'blurhash', '~> 0.1' | |||
| 
 | ||||
| gem 'active_model_serializers', '~> 0.10' | ||||
| gem 'addressable', '~> 2.7' | ||||
| gem 'bootsnap', '~> 1.5', require: false | ||||
| gem 'bootsnap', '~> 1.6.0', require: false | ||||
| gem 'browser' | ||||
| gem 'charlock_holmes', '~> 0.7.7' | ||||
| gem 'iso-639' | ||||
|  | @ -73,7 +73,7 @@ gem 'parallel', '~> 1.20' | |||
| gem 'posix-spawn' | ||||
| gem 'pundit', '~> 2.1' | ||||
| gem 'premailer-rails' | ||||
| gem 'rack-attack', '~> 6.4' | ||||
| gem 'rack-attack', '~> 6.5' | ||||
| gem 'rack-cors', '~> 1.1', require: 'rack/cors' | ||||
| gem 'rails-i18n', '~> 5.1' | ||||
| gem 'rails-settings-cached', '~> 0.6' | ||||
|  | @ -140,7 +140,7 @@ group :development do | |||
|   gem 'letter_opener', '~> 1.7' | ||||
|   gem 'letter_opener_web', '~> 1.4' | ||||
|   gem 'memory_profiler' | ||||
|   gem 'rubocop', '~> 1.8', require: false | ||||
|   gem 'rubocop', '~> 1.9', require: false | ||||
|   gem 'rubocop-rails', '~> 2.9', require: false | ||||
|   gem 'brakeman', '~> 4.10', require: false | ||||
|   gem 'bundler-audit', '~> 0.7', require: false | ||||
|  |  | |||
							
								
								
									
										44
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										44
									
								
								Gemfile.lock
								
								
								
								
							|  | @ -72,24 +72,24 @@ GEM | |||
|       activerecord (>= 3.2, < 7.0) | ||||
|       rake (>= 10.4, < 14.0) | ||||
|     arel (9.0.0) | ||||
|     ast (2.4.1) | ||||
|     ast (2.4.2) | ||||
|     attr_encrypted (3.1.0) | ||||
|       encryptor (~> 3.0.0) | ||||
|     av (0.9.0) | ||||
|       cocaine (~> 0.5.3) | ||||
|     awrence (1.1.1) | ||||
|     aws-eventstream (1.1.0) | ||||
|     aws-partitions (1.413.0) | ||||
|     aws-sdk-core (3.110.0) | ||||
|     aws-partitions (1.424.0) | ||||
|     aws-sdk-core (3.112.0) | ||||
|       aws-eventstream (~> 1, >= 1.0.2) | ||||
|       aws-partitions (~> 1, >= 1.239.0) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|       jmespath (~> 1.0) | ||||
|     aws-sdk-kms (1.40.0) | ||||
|       aws-sdk-core (~> 3, >= 3.109.0) | ||||
|     aws-sdk-kms (1.42.0) | ||||
|       aws-sdk-core (~> 3, >= 3.112.0) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|     aws-sdk-s3 (1.87.0) | ||||
|       aws-sdk-core (~> 3, >= 3.109.0) | ||||
|     aws-sdk-s3 (1.88.0) | ||||
|       aws-sdk-core (~> 3, >= 3.112.0) | ||||
|       aws-sdk-kms (~> 1) | ||||
|       aws-sigv4 (~> 1.1) | ||||
|     aws-sigv4 (1.2.2) | ||||
|  | @ -104,7 +104,7 @@ GEM | |||
|       debug_inspector (>= 0.0.1) | ||||
|     blurhash (0.1.4) | ||||
|       ffi (~> 1.10.0) | ||||
|     bootsnap (1.5.1) | ||||
|     bootsnap (1.6.0) | ||||
|       msgpack (~> 1.0) | ||||
|     brakeman (4.10.1) | ||||
|     browser (4.2.0) | ||||
|  | @ -275,7 +275,7 @@ GEM | |||
|     httplog (1.4.3) | ||||
|       rack (>= 1.0) | ||||
|       rainbow (>= 2.0.0) | ||||
|     i18n (1.8.7) | ||||
|     i18n (1.8.8) | ||||
|       concurrent-ruby (~> 1.0) | ||||
|     i18n-tasks (0.9.33) | ||||
|       activesupport (>= 4.0.2) | ||||
|  | @ -293,7 +293,7 @@ GEM | |||
|     jmespath (1.4.0) | ||||
|     json (2.3.1) | ||||
|     json-canonicalization (0.2.0) | ||||
|     json-ld (3.1.7) | ||||
|     json-ld (3.1.8) | ||||
|       htmlentities (~> 4.3) | ||||
|       json-canonicalization (~> 0.2) | ||||
|       link_header (~> 0.0, >= 0.0.8) | ||||
|  | @ -354,7 +354,7 @@ GEM | |||
|     mini_mime (1.0.2) | ||||
|     mini_portile2 (2.5.0) | ||||
|     minitest (5.14.3) | ||||
|     msgpack (1.3.3) | ||||
|     msgpack (1.4.2) | ||||
|     multi_json (1.15.0) | ||||
|     multipart-post (2.1.1) | ||||
|     net-ldap (0.17.0) | ||||
|  | @ -408,9 +408,9 @@ GEM | |||
|     pastel (0.8.0) | ||||
|       tty-color (~> 0.5) | ||||
|     pg (1.2.3) | ||||
|     pghero (2.7.3) | ||||
|     pghero (2.7.4) | ||||
|       activerecord (>= 5) | ||||
|     pkg-config (1.4.4) | ||||
|     pkg-config (1.4.5) | ||||
|     pluck_each (0.1.3) | ||||
|       activerecord (> 3.2.0) | ||||
|       activesupport (> 3.0.0) | ||||
|  | @ -439,7 +439,7 @@ GEM | |||
|     raabro (1.3.3) | ||||
|     racc (1.5.2) | ||||
|     rack (2.2.3) | ||||
|     rack-attack (6.4.0) | ||||
|     rack-attack (6.5.0) | ||||
|       rack (>= 1.0, < 3) | ||||
|     rack-cors (1.1.1) | ||||
|       rack (>= 2.0.0) | ||||
|  | @ -482,7 +482,7 @@ GEM | |||
|       thor (>= 0.19.0, < 2.0) | ||||
|     rainbow (3.0.0) | ||||
|     rake (13.0.3) | ||||
|     rdf (3.1.8) | ||||
|     rdf (3.1.10) | ||||
|       hamster (~> 3.0) | ||||
|       link_header (~> 0.0, >= 0.0.8) | ||||
|     rdf-normalize (0.4.0) | ||||
|  | @ -496,7 +496,7 @@ GEM | |||
|     redis-activesupport (5.2.0) | ||||
|       activesupport (>= 3, < 7) | ||||
|       redis-store (>= 1.3, < 2) | ||||
|     redis-namespace (1.8.0) | ||||
|     redis-namespace (1.8.1) | ||||
|       redis (>= 3.0.4) | ||||
|     redis-rack (2.1.3) | ||||
|       rack (>= 2.0.8, < 3) | ||||
|  | @ -542,7 +542,7 @@ GEM | |||
|     rspec-support (3.10.1) | ||||
|     rspec_junit_formatter (0.4.1) | ||||
|       rspec-core (>= 2, < 4, != 2.12.0) | ||||
|     rubocop (1.8.1) | ||||
|     rubocop (1.9.1) | ||||
|       parallel (~> 1.10) | ||||
|       parser (>= 3.0.0.0) | ||||
|       rainbow (>= 2.2.2, < 4.0) | ||||
|  | @ -551,7 +551,7 @@ GEM | |||
|       rubocop-ast (>= 1.2.0, < 2.0) | ||||
|       ruby-progressbar (~> 1.7) | ||||
|       unicode-display_width (>= 1.4.0, < 3.0) | ||||
|     rubocop-ast (1.4.0) | ||||
|     rubocop-ast (1.4.1) | ||||
|       parser (>= 2.7.1.5) | ||||
|     rubocop-rails (2.9.1) | ||||
|       activesupport (>= 4.2.0) | ||||
|  | @ -693,11 +693,11 @@ DEPENDENCIES | |||
|   active_record_query_trace (~> 1.8) | ||||
|   addressable (~> 2.7) | ||||
|   annotate (~> 3.1) | ||||
|   aws-sdk-s3 (~> 1.87) | ||||
|   aws-sdk-s3 (~> 1.88) | ||||
|   better_errors (~> 2.9) | ||||
|   binding_of_caller (~> 1.0) | ||||
|   blurhash (~> 0.1) | ||||
|   bootsnap (~> 1.5) | ||||
|   bootsnap (~> 1.6.0) | ||||
|   brakeman (~> 4.10) | ||||
|   browser | ||||
|   bullet (~> 6.1) | ||||
|  | @ -777,7 +777,7 @@ DEPENDENCIES | |||
|   puma (~> 5.1) | ||||
|   pundit (~> 2.1) | ||||
|   rack (~> 2.2.3) | ||||
|   rack-attack (~> 6.4) | ||||
|   rack-attack (~> 6.5) | ||||
|   rack-cors (~> 1.1) | ||||
|   rails (~> 5.2.4.4) | ||||
|   rails-controller-testing (~> 1.0) | ||||
|  | @ -792,7 +792,7 @@ DEPENDENCIES | |||
|   rspec-rails (~> 4.0) | ||||
|   rspec-sidekiq (~> 3.1) | ||||
|   rspec_junit_formatter (~> 0.4) | ||||
|   rubocop (~> 1.8) | ||||
|   rubocop (~> 1.9) | ||||
|   rubocop-rails (~> 2.9) | ||||
|   ruby-progressbar (~> 1.11) | ||||
|   sanitize (~> 5.2) | ||||
|  |  | |||
|  | @ -72,10 +72,12 @@ bundle install | |||
| yarn install | ||||
| 
 | ||||
| # Build Mastodon | ||||
| export RAILS_ENV=development  | ||||
| export $(cat ".env.vagrant" | xargs) | ||||
| bundle exec rails db:setup | ||||
| 
 | ||||
| # Configure automatic loading of environment variable | ||||
| echo 'export RAILS_ENV=development' >> ~/.bash_profile | ||||
| echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile | ||||
| 
 | ||||
| SCRIPT | ||||
|  |  | |||
|  | @ -2,17 +2,16 @@ | |||
| 
 | ||||
| class Api::Web::SettingsController < Api::Web::BaseController | ||||
|   before_action :require_user! | ||||
|   before_action :set_setting | ||||
| 
 | ||||
|   def update | ||||
|     setting.data = params[:data] | ||||
|     setting.save! | ||||
| 
 | ||||
|     @setting.update!(data: params[:data]) | ||||
|     render_empty | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def setting | ||||
|     @_setting ||= ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user) | ||||
|   def set_setting | ||||
|     @setting = ::Web::Setting.where(user: current_user).first_or_initialize(user: current_user) | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -44,7 +44,7 @@ class ApplicationController < ActionController::Base | |||
|   private | ||||
| 
 | ||||
|   def https_enabled? | ||||
|     Rails.env.production? && !request.path.start_with?('/health') | ||||
|     Rails.env.production? && !request.path.start_with?('/health') && !request.headers["Host"].ends_with?(".onion") | ||||
|   end | ||||
| 
 | ||||
|   def authorized_fetch_mode? | ||||
|  |  | |||
|  | @ -13,7 +13,7 @@ class InstanceActorsController < ApplicationController | |||
|   private | ||||
| 
 | ||||
|   def set_account | ||||
|     @account = Account.find(-99) | ||||
|     @account = Account.representative | ||||
|   end | ||||
| 
 | ||||
|   def restrict_fields_to | ||||
|  |  | |||
|  | @ -0,0 +1,13 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module MascotHelper | ||||
|   def mascot_url | ||||
|     full_asset_url(instance_presenter.mascot&.file&.url || asset_pack_path('media/images/elephant_ui_plane.svg')) | ||||
|   end | ||||
| 
 | ||||
|   private | ||||
| 
 | ||||
|   def instance_presenter | ||||
|     @instance_presenter ||= InstancePresenter.new | ||||
|   end | ||||
| end | ||||
|  | @ -0,0 +1,29 @@ | |||
| import { openModal } from './modal'; | ||||
| 
 | ||||
| export const BOOSTS_INIT_MODAL = 'BOOSTS_INIT_MODAL'; | ||||
| export const BOOSTS_CHANGE_PRIVACY = 'BOOSTS_CHANGE_PRIVACY'; | ||||
| 
 | ||||
| export function initBoostModal(props) { | ||||
|   return (dispatch, getState) => { | ||||
|     const default_privacy = getState().getIn(['compose', 'default_privacy']); | ||||
| 
 | ||||
|     const privacy = props.status.get('visibility') === 'private' ? 'private' : default_privacy; | ||||
| 
 | ||||
|     dispatch({ | ||||
|       type: BOOSTS_INIT_MODAL, | ||||
|       privacy | ||||
|     }); | ||||
| 
 | ||||
|     dispatch(openModal('BOOST', props)); | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| export function changeBoostPrivacy(privacy) { | ||||
|   return dispatch => { | ||||
|     dispatch({ | ||||
|       type: BOOSTS_CHANGE_PRIVACY, | ||||
|       privacy, | ||||
|     }); | ||||
|   }; | ||||
| } | ||||
|  | @ -41,11 +41,11 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST'; | |||
| export const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS'; | ||||
| export const UNBOOKMARK_FAIL    = 'UNBOOKMARKED_FAIL'; | ||||
| 
 | ||||
| export function reblog(status) { | ||||
| export function reblog(status, visibility) { | ||||
|   return function (dispatch, getState) { | ||||
|     dispatch(reblogRequest(status)); | ||||
| 
 | ||||
|     api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then(function (response) { | ||||
|     api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`, { visibility }).then(function (response) { | ||||
|       // The reblog API method returns a new status wrapped around the original. In this case we are only
 | ||||
|       // interested in how the original is modified, hence passing it skipping the wrapper
 | ||||
|       dispatch(importFetchedStatus(response.data.reblog)); | ||||
|  |  | |||
|  | @ -177,7 +177,6 @@ export default class Dropdown extends React.PureComponent { | |||
|     disabled: PropTypes.bool, | ||||
|     status: ImmutablePropTypes.map, | ||||
|     isUserTouching: PropTypes.func, | ||||
|     isModalOpen: PropTypes.bool.isRequired, | ||||
|     onOpen: PropTypes.func.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     dropdownPlacement: PropTypes.string, | ||||
|  |  | |||
|  | @ -223,10 +223,11 @@ class StatusActionBar extends ImmutablePureComponent { | |||
|   render () { | ||||
|     const { status, relationship, intl, withDismiss, scrollKey } = this.props; | ||||
| 
 | ||||
|     const mutingConversation = status.get('muted'); | ||||
|     const anonymousAccess    = !me; | ||||
|     const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility')); | ||||
|     const mutingConversation = status.get('muted'); | ||||
|     const account            = status.get('account'); | ||||
|     const writtenByMe        = status.getIn(['account', 'id']) === me; | ||||
| 
 | ||||
|     let menu = []; | ||||
| 
 | ||||
|  | @ -237,19 +238,22 @@ class StatusActionBar extends ImmutablePureComponent { | |||
|       menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); | ||||
|     } | ||||
| 
 | ||||
|     menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick }); | ||||
|     menu.push(null); | ||||
| 
 | ||||
|     if (status.getIn(['account', 'id']) === me || withDismiss) { | ||||
|     menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClick }); | ||||
| 
 | ||||
|     if (writtenByMe && publicStatus) { | ||||
|       menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); | ||||
|     } | ||||
| 
 | ||||
|     menu.push(null); | ||||
| 
 | ||||
|     if (writtenByMe || withDismiss) { | ||||
|       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); | ||||
|       menu.push(null); | ||||
|     } | ||||
| 
 | ||||
|     if (status.getIn(['account', 'id']) === me) { | ||||
|       if (publicStatus) { | ||||
|         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); | ||||
|       } | ||||
| 
 | ||||
|     if (writtenByMe) { | ||||
|       menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); | ||||
|       menu.push({ text: intl.formatMessage(messages.redraft), action: this.handleRedraftClick }); | ||||
|     } else { | ||||
|  |  | |||
|  | @ -6,7 +6,6 @@ import DropdownMenu from '../components/dropdown_menu'; | |||
| import { isUserTouching } from '../is_mobile'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   isModalOpen: state.get('modal').modalType === 'ACTIONS', | ||||
|   dropdownPlacement: state.getIn(['dropdown_menu', 'placement']), | ||||
|   openDropdownId: state.getIn(['dropdown_menu', 'openId']), | ||||
|   openedViaKeyboard: state.getIn(['dropdown_menu', 'keyboard']), | ||||
|  |  | |||
|  | @ -35,6 +35,7 @@ import { | |||
| } from '../actions/domain_blocks'; | ||||
| import { initMuteModal } from '../actions/mutes'; | ||||
| import { initBlockModal } from '../actions/blocks'; | ||||
| import { initBoostModal } from '../actions/boosts'; | ||||
| import { initReport } from '../actions/reports'; | ||||
| import { openModal } from '../actions/modal'; | ||||
| import { deployPictureInPicture } from '../actions/picture_in_picture'; | ||||
|  | @ -82,11 +83,11 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   onModalReblog (status) { | ||||
|   onModalReblog (status, privacy) { | ||||
|     if (status.get('reblogged')) { | ||||
|       dispatch(unreblog(status)); | ||||
|     } else { | ||||
|       dispatch(reblog(status)); | ||||
|       dispatch(reblog(status, privacy)); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|  | @ -94,7 +95,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     if ((e && e.shiftKey) || !boostModal) { | ||||
|       this.onModalReblog(status); | ||||
|     } else { | ||||
|       dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | ||||
|       dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); | ||||
|     } | ||||
|   }, | ||||
| 
 | ||||
|  |  | |||
|  | @ -127,7 +127,7 @@ class PrivacyDropdownMenu extends React.PureComponent { | |||
|           // It should not be transformed when mounting because the resulting
 | ||||
|           // size will be used to determine the coordinate of the menu by
 | ||||
|           // react-overlays
 | ||||
|           <div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null, zIndex: 2 }} role='listbox' ref={this.setRef}> | ||||
|           <div className={`privacy-dropdown__dropdown ${placement}`} style={{ ...style, opacity: opacity, transform: mounted ? `scale(${scaleX}, ${scaleY})` : null }} role='listbox' ref={this.setRef}> | ||||
|             {items.map(item => ( | ||||
|               <div role='option' tabIndex='0' key={item.value} data-index={item.value} onKeyDown={this.handleKeyDown} onClick={this.handleClick} className={classNames('privacy-dropdown__option', { active: item.value === value })} aria-selected={item.value === value} ref={item.value === value ? this.setFocusRef : null}> | ||||
|                 <div className='privacy-dropdown__option__icon'> | ||||
|  | @ -153,11 +153,12 @@ class PrivacyDropdown extends React.PureComponent { | |||
| 
 | ||||
|   static propTypes = { | ||||
|     isUserTouching: PropTypes.func, | ||||
|     isModalOpen: PropTypes.bool.isRequired, | ||||
|     onModalOpen: PropTypes.func, | ||||
|     onModalClose: PropTypes.func, | ||||
|     value: PropTypes.string.isRequired, | ||||
|     onChange: PropTypes.func.isRequired, | ||||
|     noDirect: PropTypes.bool, | ||||
|     container: PropTypes.func, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -167,7 +168,7 @@ class PrivacyDropdown extends React.PureComponent { | |||
|   }; | ||||
| 
 | ||||
|   handleToggle = ({ target }) => { | ||||
|     if (this.props.isUserTouching()) { | ||||
|     if (this.props.isUserTouching && this.props.isUserTouching()) { | ||||
|       if (this.state.open) { | ||||
|         this.props.onModalClose(); | ||||
|       } else { | ||||
|  | @ -236,12 +237,17 @@ class PrivacyDropdown extends React.PureComponent { | |||
|       { icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) }, | ||||
|       { icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) }, | ||||
|       { icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) }, | ||||
|       { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, | ||||
|     ]; | ||||
| 
 | ||||
|     if (!this.props.noDirect) { | ||||
|       this.options.push( | ||||
|         { icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) }, | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { value, intl } = this.props; | ||||
|     const { value, container, intl } = this.props; | ||||
|     const { open, placement } = this.state; | ||||
| 
 | ||||
|     const valueOption = this.options.find(item => item.value === value); | ||||
|  | @ -264,7 +270,7 @@ class PrivacyDropdown extends React.PureComponent { | |||
|           /> | ||||
|         </div> | ||||
| 
 | ||||
|         <Overlay show={open} placement={placement} target={this}> | ||||
|         <Overlay show={open} placement={placement} target={this} container={container}> | ||||
|           <PrivacyDropdownMenu | ||||
|             items={this.options} | ||||
|             value={value} | ||||
|  |  | |||
|  | @ -5,7 +5,6 @@ import { openModal, closeModal } from '../../../actions/modal'; | |||
| import { isUserTouching } from '../../../is_mobile'; | ||||
| 
 | ||||
| const mapStateToProps = state => ({ | ||||
|   isModalOpen: state.get('modal').modalType === 'ACTIONS', | ||||
|   value: state.getIn(['compose', 'privacy']), | ||||
| }); | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| import { connect } from 'react-redux'; | ||||
| import { makeGetNotification, makeGetStatus } from '../../../selectors'; | ||||
| import Notification from '../components/notification'; | ||||
| import { initBoostModal } from '../../../actions/boosts'; | ||||
| import { openModal } from '../../../actions/modal'; | ||||
| import { mentionCompose } from '../../../actions/compose'; | ||||
| import { | ||||
|  | @ -35,8 +36,8 @@ const mapDispatchToProps = dispatch => ({ | |||
|     dispatch(mentionCompose(account, router)); | ||||
|   }, | ||||
| 
 | ||||
|   onModalReblog (status) { | ||||
|     dispatch(reblog(status)); | ||||
|   onModalReblog (status, privacy) { | ||||
|     dispatch(reblog(status, privacy)); | ||||
|   }, | ||||
| 
 | ||||
|   onReblog (status, e) { | ||||
|  | @ -46,7 +47,7 @@ const mapDispatchToProps = dispatch => ({ | |||
|       if (e.shiftKey || !boostModal) { | ||||
|         this.onModalReblog(status); | ||||
|       } else { | ||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | ||||
|         dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|  |  | |||
|  | @ -10,6 +10,7 @@ import { defineMessages, injectIntl } from 'react-intl'; | |||
| import { replyCompose } from 'mastodon/actions/compose'; | ||||
| import { reblog, favourite, unreblog, unfavourite } from 'mastodon/actions/interactions'; | ||||
| import { makeGetStatus } from 'mastodon/selectors'; | ||||
| import { initBoostModal } from 'mastodon/actions/boosts'; | ||||
| import { openModal } from 'mastodon/actions/modal'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|  | @ -89,9 +90,9 @@ class Footer extends ImmutablePureComponent { | |||
|     } | ||||
|   }; | ||||
| 
 | ||||
|   _performReblog = () => { | ||||
|     const { dispatch, status } = this.props; | ||||
|     dispatch(reblog(status)); | ||||
|   _performReblog = (status, privacy) => { | ||||
|     const { dispatch } = this.props; | ||||
|     dispatch(reblog(status, privacy)); | ||||
|   } | ||||
| 
 | ||||
|   handleReblogClick = e => { | ||||
|  | @ -100,9 +101,9 @@ class Footer extends ImmutablePureComponent { | |||
|     if (status.get('reblogged')) { | ||||
|       dispatch(unreblog(status)); | ||||
|     } else if ((e && e.shiftKey) || !boostModal) { | ||||
|       this._performReblog(); | ||||
|       this._performReblog(status); | ||||
|     } else { | ||||
|       dispatch(openModal('BOOST', { status, onReblog: this._performReblog })); | ||||
|       dispatch(initBoostModal({ status, onReblog: this._performReblog })); | ||||
|     } | ||||
|   }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -187,9 +187,10 @@ class ActionBar extends React.PureComponent { | |||
|   render () { | ||||
|     const { status, relationship, intl } = this.props; | ||||
| 
 | ||||
|     const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); | ||||
|     const publicStatus       = ['public', 'unlisted'].includes(status.get('visibility')); | ||||
|     const mutingConversation = status.get('muted'); | ||||
|     const account            = status.get('account'); | ||||
|     const writtenByMe        = status.getIn(['account', 'id']) === me; | ||||
| 
 | ||||
|     let menu = []; | ||||
| 
 | ||||
|  | @ -199,12 +200,12 @@ class ActionBar extends React.PureComponent { | |||
|       menu.push(null); | ||||
|     } | ||||
| 
 | ||||
|     if (me === status.getIn(['account', 'id'])) { | ||||
|     if (writtenByMe) { | ||||
|       if (publicStatus) { | ||||
|         menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick }); | ||||
|         menu.push(null); | ||||
|       } | ||||
| 
 | ||||
|       menu.push(null); | ||||
|       menu.push({ text: intl.formatMessage(mutingConversation ? messages.unmuteConversation : messages.muteConversation), action: this.handleConversationMuteClick }); | ||||
|       menu.push(null); | ||||
|       menu.push({ text: intl.formatMessage(messages.delete), action: this.handleDeleteClick }); | ||||
|  |  | |||
|  | @ -23,6 +23,7 @@ import { | |||
| } from '../../../actions/statuses'; | ||||
| import { initMuteModal } from '../../../actions/mutes'; | ||||
| import { initBlockModal } from '../../../actions/blocks'; | ||||
| import { initBoostModal } from '../../../actions/boosts'; | ||||
| import { initReport } from '../../../actions/reports'; | ||||
| import { openModal } from '../../../actions/modal'; | ||||
| import { defineMessages, injectIntl } from 'react-intl'; | ||||
|  | @ -68,8 +69,8 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|     }); | ||||
|   }, | ||||
| 
 | ||||
|   onModalReblog (status) { | ||||
|     dispatch(reblog(status)); | ||||
|   onModalReblog (status, privacy) { | ||||
|     dispatch(reblog(status, privacy)); | ||||
|   }, | ||||
| 
 | ||||
|   onReblog (status, e) { | ||||
|  | @ -79,7 +80,7 @@ const mapDispatchToProps = (dispatch, { intl }) => ({ | |||
|       if (e.shiftKey || !boostModal) { | ||||
|         this.onModalReblog(status); | ||||
|       } else { | ||||
|         dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog })); | ||||
|         dispatch(initBoostModal({ status, onReblog: this.onModalReblog })); | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|  |  | |||
|  | @ -42,6 +42,7 @@ import { | |||
| } from '../../actions/domain_blocks'; | ||||
| import { initMuteModal } from '../../actions/mutes'; | ||||
| import { initBlockModal } from '../../actions/blocks'; | ||||
| import { initBoostModal } from '../../actions/boosts'; | ||||
| import { initReport } from '../../actions/reports'; | ||||
| import { makeGetStatus, makeGetPictureInPicture } from '../../selectors'; | ||||
| import { ScrollContainer } from 'react-router-scroll-4'; | ||||
|  | @ -234,8 +235,8 @@ class Status extends ImmutablePureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   handleModalReblog = (status) => { | ||||
|     this.props.dispatch(reblog(status)); | ||||
|   handleModalReblog = (status, privacy) => { | ||||
|     this.props.dispatch(reblog(status, privacy)); | ||||
|   } | ||||
| 
 | ||||
|   handleReblogClick = (status, e) => { | ||||
|  | @ -245,7 +246,7 @@ class Status extends ImmutablePureComponent { | |||
|       if ((e && e.shiftKey) || !boostModal) { | ||||
|         this.handleModalReblog(status); | ||||
|       } else { | ||||
|         this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog })); | ||||
|         this.props.dispatch(initBoostModal({ status, onReblog: this.handleModalReblog })); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import React from 'react'; | ||||
| import { connect } from 'react-redux'; | ||||
| import ImmutablePropTypes from 'react-immutable-proptypes'; | ||||
| import PropTypes from 'prop-types'; | ||||
| import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; | ||||
|  | @ -10,7 +11,9 @@ import DisplayName from '../../../components/display_name'; | |||
| import ImmutablePureComponent from 'react-immutable-pure-component'; | ||||
| import Icon from 'mastodon/components/icon'; | ||||
| import AttachmentList from 'mastodon/components/attachment_list'; | ||||
| import PrivacyDropdown from 'mastodon/features/compose/components/privacy_dropdown'; | ||||
| import classNames from 'classnames'; | ||||
| import { changeBoostPrivacy } from 'mastodon/actions/boosts'; | ||||
| 
 | ||||
| const messages = defineMessages({ | ||||
|   cancel_reblog: { id: 'status.cancel_reblog_private', defaultMessage: 'Unboost' }, | ||||
|  | @ -21,7 +24,22 @@ const messages = defineMessages({ | |||
|   direct_short: { id: 'privacy.direct.short', defaultMessage: 'Direct' }, | ||||
| }); | ||||
| 
 | ||||
| export default @injectIntl | ||||
| const mapStateToProps = state => { | ||||
|   return { | ||||
|     privacy: state.getIn(['boosts', 'new', 'privacy']), | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| const mapDispatchToProps = dispatch => { | ||||
|   return { | ||||
|     onChangeBoostPrivacy(value) { | ||||
|       dispatch(changeBoostPrivacy(value)); | ||||
|     }, | ||||
|   }; | ||||
| }; | ||||
| 
 | ||||
| export default @connect(mapStateToProps, mapDispatchToProps) | ||||
| @injectIntl | ||||
| class BoostModal extends ImmutablePureComponent { | ||||
| 
 | ||||
|   static contextTypes = { | ||||
|  | @ -32,6 +50,8 @@ class BoostModal extends ImmutablePureComponent { | |||
|     status: ImmutablePropTypes.map.isRequired, | ||||
|     onReblog: PropTypes.func.isRequired, | ||||
|     onClose: PropTypes.func.isRequired, | ||||
|     onChangeBoostPrivacy: PropTypes.func.isRequired, | ||||
|     privacy: PropTypes.string.isRequired, | ||||
|     intl: PropTypes.object.isRequired, | ||||
|   }; | ||||
| 
 | ||||
|  | @ -40,7 +60,7 @@ class BoostModal extends ImmutablePureComponent { | |||
|   } | ||||
| 
 | ||||
|   handleReblog = () => { | ||||
|     this.props.onReblog(this.props.status); | ||||
|     this.props.onReblog(this.props.status, this.props.privacy); | ||||
|     this.props.onClose(); | ||||
|   } | ||||
| 
 | ||||
|  | @ -52,12 +72,16 @@ class BoostModal extends ImmutablePureComponent { | |||
|     } | ||||
|   } | ||||
| 
 | ||||
|   _findContainer = () => { | ||||
|     return document.getElementsByClassName('modal-root__container')[0]; | ||||
|   }; | ||||
| 
 | ||||
|   setRef = (c) => { | ||||
|     this.button = c; | ||||
|   } | ||||
| 
 | ||||
|   render () { | ||||
|     const { status, intl } = this.props; | ||||
|     const { status, privacy, intl } = this.props; | ||||
|     const buttonText = status.get('reblogged') ? messages.cancel_reblog : messages.reblog; | ||||
| 
 | ||||
|     const visibilityIconInfo = { | ||||
|  | @ -102,6 +126,14 @@ class BoostModal extends ImmutablePureComponent { | |||
| 
 | ||||
|         <div className='boost-modal__action-bar'> | ||||
|           <div><FormattedMessage id='boost_modal.combo' defaultMessage='You can press {combo} to skip this next time' values={{ combo: <span>Shift + <Icon id='retweet' /></span> }} /></div> | ||||
|           {status.get('visibility') !== 'private' && !status.get('reblogged') && ( | ||||
|             <PrivacyDropdown | ||||
|               noDirect | ||||
|               value={privacy} | ||||
|               container={this._findContainer} | ||||
|               onChange={this.props.onChangeBoostPrivacy} | ||||
|             /> | ||||
|           )} | ||||
|           <Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} /> | ||||
|         </div> | ||||
|       </div> | ||||
|  |  | |||
|  | @ -0,0 +1,25 @@ | |||
| import Immutable from 'immutable'; | ||||
| 
 | ||||
| import { | ||||
|   BOOSTS_INIT_MODAL, | ||||
|   BOOSTS_CHANGE_PRIVACY, | ||||
| } from 'mastodon/actions/boosts'; | ||||
| 
 | ||||
| const initialState = Immutable.Map({ | ||||
|   new: Immutable.Map({ | ||||
|     privacy: 'public', | ||||
|   }), | ||||
| }); | ||||
| 
 | ||||
| export default function mutes(state = initialState, action) { | ||||
|   switch (action.type) { | ||||
|   case BOOSTS_INIT_MODAL: | ||||
|     return state.withMutations((state) => { | ||||
|       state.setIn(['new', 'privacy'], action.privacy); | ||||
|     }); | ||||
|   case BOOSTS_CHANGE_PRIVACY: | ||||
|     return state.setIn(['new', 'privacy'], action.privacy); | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
| } | ||||
|  | @ -16,6 +16,7 @@ import push_notifications from './push_notifications'; | |||
| import status_lists from './status_lists'; | ||||
| import mutes from './mutes'; | ||||
| import blocks from './blocks'; | ||||
| import boosts from './boosts'; | ||||
| import reports from './reports'; | ||||
| import contexts from './contexts'; | ||||
| import compose from './compose'; | ||||
|  | @ -57,6 +58,7 @@ const reducers = { | |||
|   push_notifications, | ||||
|   mutes, | ||||
|   blocks, | ||||
|   boosts, | ||||
|   reports, | ||||
|   contexts, | ||||
|   compose, | ||||
|  |  | |||
|  | @ -1,4 +1,5 @@ | |||
| import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; | ||||
| import { TIMELINE_DELETE } from '../actions/timelines'; | ||||
| 
 | ||||
| const initialState = { | ||||
|   modalType: null, | ||||
|  | @ -11,6 +12,8 @@ export default function modal(state = initialState, action) { | |||
|     return { modalType: action.modalType, modalProps: action.modalProps }; | ||||
|   case MODAL_CLOSE: | ||||
|     return (action.modalType === undefined || action.modalType === state.modalType) ? initialState : state; | ||||
|   case TIMELINE_DELETE: | ||||
|     return (state.modalProps.statusId === action.id) ? initialState : state; | ||||
|   default: | ||||
|     return state; | ||||
|   } | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import EXIF from 'exif-js'; | ||||
| 
 | ||||
| const MAX_IMAGE_PIXELS = 1638400; // 1280x1280px
 | ||||
| const MAX_IMAGE_PIXELS = 2073600; // 1920x1080px
 | ||||
| 
 | ||||
| const _browser_quirks = {}; | ||||
| 
 | ||||
|  |  | |||
|  | @ -4209,6 +4209,7 @@ a.status-card.compact:hover { | |||
|   border-radius: 4px; | ||||
|   margin-left: 40px; | ||||
|   overflow: hidden; | ||||
|   z-index: 2; | ||||
| 
 | ||||
|   &.top { | ||||
|     transform-origin: 50% 100%; | ||||
|  | @ -4219,6 +4220,15 @@ a.status-card.compact:hover { | |||
|   } | ||||
| } | ||||
| 
 | ||||
| .modal-root__container .privacy-dropdown { | ||||
|   flex-grow: 0; | ||||
| } | ||||
| 
 | ||||
| .modal-root__container .privacy-dropdown__dropdown { | ||||
|   pointer-events: auto; | ||||
|   z-index: 9999; | ||||
| } | ||||
| 
 | ||||
| .privacy-dropdown__option { | ||||
|   color: $inverted-text-color; | ||||
|   padding: 10px; | ||||
|  |  | |||
|  | @ -12,10 +12,19 @@ | |||
|   flex-direction: column; | ||||
|   justify-content: flex-end; | ||||
| 
 | ||||
|   > * { | ||||
|   > div { | ||||
|     flex: 1; | ||||
|     max-height: 235px; | ||||
|     background: url('~images/elephant_ui_plane.svg') no-repeat left bottom / contain; | ||||
|     position: relative; | ||||
| 
 | ||||
|     img { | ||||
|       max-height: 100%; | ||||
|       max-width: 100%; | ||||
|       height: 100%; | ||||
|       position: absolute; | ||||
|       bottom: 0; | ||||
|       left: 0; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -6,7 +6,14 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity | |||
|   def perform | ||||
|     target_account = account_from_uri(object_uri) | ||||
| 
 | ||||
|     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) || @account.requested?(target_account) | ||||
|     return if target_account.nil? || !target_account.local? || delete_arrived_first?(@json['id']) | ||||
| 
 | ||||
|     # Update id of already-existing follow requests | ||||
|     existing_follow_request = ::FollowRequest.find_by(account: @account, target_account: target_account) | ||||
|     unless existing_follow_request.nil? | ||||
|       existing_follow_request.update!(uri: @json['id']) | ||||
|       return | ||||
|     end | ||||
| 
 | ||||
|     if target_account.blocking?(@account) || target_account.domain_blocking?(@account.domain) || target_account.moved? || target_account.instance_actor? | ||||
|       reject_follow_request!(target_account) | ||||
|  | @ -14,7 +21,9 @@ class ActivityPub::Activity::Follow < ActivityPub::Activity | |||
|     end | ||||
| 
 | ||||
|     # Fast-forward repeat follow requests | ||||
|     if @account.following?(target_account) | ||||
|     existing_follow = ::Follow.find_by(account: @account, target_account: target_account) | ||||
|     unless existing_follow.nil? | ||||
|       existing_follow.update!(uri: @json['id']) | ||||
|       AuthorizeFollowService.new.call(@account, target_account, skip_follow_request: true, follow_request_uri: @json['id']) | ||||
|       return | ||||
|     end | ||||
|  |  | |||
|  | @ -88,10 +88,18 @@ class Webfinger | |||
|   end | ||||
| 
 | ||||
|   def standard_url | ||||
|     "https://#{@domain}/.well-known/webfinger?resource=#{@uri}" | ||||
|     if @domain.ends_with? ".onion" | ||||
|       "http://#{@domain}/.well-known/webfinger?resource=#{@uri}" | ||||
|     else | ||||
|       "https://#{@domain}/.well-known/webfinger?resource=#{@uri}" | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def host_meta_url | ||||
|     "https://#{@domain}/.well-known/host-meta" | ||||
|     if @domain.ends_with? ".onion" | ||||
|       "http://#{@domain}/.well-known/host-meta" | ||||
|     else | ||||
|       "https://#{@domain}/.well-known/host-meta" | ||||
|     end | ||||
|   end | ||||
| end | ||||
|  |  | |||
|  | @ -14,6 +14,8 @@ module AccountFinderConcern | |||
| 
 | ||||
|     def representative | ||||
|       Account.find(-99) | ||||
|     rescue ActiveRecord::RecordNotFound | ||||
|       Account.create!(id: -99, actor_type: 'Application', locked: true, username: Rails.configuration.x.local_domain) | ||||
|     end | ||||
| 
 | ||||
|     def find_local(username) | ||||
|  |  | |||
|  | @ -59,7 +59,7 @@ class MediaAttachment < ApplicationRecord | |||
| 
 | ||||
|   IMAGE_STYLES = { | ||||
|     original: { | ||||
|       pixels: 1_638_400, # 1280x1280px | ||||
|       pixels: 2_073_600, # 1920x1080px | ||||
|       file_geometry_parser: FastGeometryParser, | ||||
|     }.freeze, | ||||
| 
 | ||||
|  |  | |||
|  | @ -38,7 +38,17 @@ class FetchOEmbedService | |||
| 
 | ||||
|     return if @endpoint_url.blank? | ||||
| 
 | ||||
|     @endpoint_url = (Addressable::URI.parse(@url) + @endpoint_url).to_s | ||||
|     @endpoint_url = begin | ||||
|       base_url = Addressable::URI.parse(@url) | ||||
| 
 | ||||
|       # If the OEmbed endpoint is given as http but the URL we opened | ||||
|       # was served over https, we can assume OEmbed will be available | ||||
|       # through https as well | ||||
| 
 | ||||
|       (base_url + @endpoint_url).tap do |absolute_url| | ||||
|         absolute_url.scheme = base_url.scheme if base_url.scheme == 'https' | ||||
|       end.to_s | ||||
|     end | ||||
| 
 | ||||
|     cache_endpoint! | ||||
|   rescue Addressable::URI::InvalidURIError | ||||
|  |  | |||
|  | @ -11,5 +11,6 @@ | |||
|   .container-alt= yield | ||||
|   .modal-layout__mastodon | ||||
|     %div | ||||
|       %img{alt:'', draggable:'false', src:"#{mascot_url}"} | ||||
| 
 | ||||
| = render template: 'layouts/application' | ||||
|  |  | |||
|  | @ -25,6 +25,8 @@ require_relative '../lib/devise/two_factor_pam_authenticatable' | |||
| require_relative '../lib/chewy/strategy/custom_sidekiq' | ||||
| require_relative '../lib/webpacker/manifest_extensions' | ||||
| require_relative '../lib/webpacker/helper_extensions' | ||||
| require_relative '../lib/action_dispatch/cookie_jar_extensions' | ||||
| require_relative '../lib/rails/engine_extensions' | ||||
| 
 | ||||
| Dotenv::Railtie.load | ||||
| 
 | ||||
|  |  | |||
|  | @ -0,0 +1,15 @@ | |||
| # frozen_string_literal: true | ||||
| 
 | ||||
| module ActionDispatch | ||||
|   module CookieJarExtensions | ||||
|     private | ||||
| 
 | ||||
|     # Monkey-patch ActionDispatch to serve secure cookies to Tor Hidden Service | ||||
|     # users. Otherwise, ActionDispatch would drop the cookie over HTTP. | ||||
|     def write_cookie?(*) | ||||
|       request.headers['Host'].ends_with?('.onion') || super | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| ActionDispatch::Cookies::CookieJar.prepend(ActionDispatch::CookieJarExtensions) | ||||
|  | @ -0,0 +1,11 @@ | |||
| module Rails | ||||
|   module EngineExtensions | ||||
|     # Rewrite task loading code to filter digitalocean.rake task | ||||
|     def run_tasks_blocks(app) | ||||
|       Railtie.instance_method(:run_tasks_blocks).bind(self).call(app) | ||||
|       paths["lib/tasks"].existent.reject { |ext| ext.end_with?('digitalocean.rake') }.sort.each { |ext| load(ext) } | ||||
|     end | ||||
|   end | ||||
| end | ||||
| 
 | ||||
| Rails::Engine.prepend(Rails::EngineExtensions) | ||||
							
								
								
									
										30
									
								
								package.json
								
								
								
								
							
							
						
						
									
										30
									
								
								package.json
								
								
								
								
							|  | @ -60,18 +60,18 @@ | |||
|   }, | ||||
|   "private": true, | ||||
|   "dependencies": { | ||||
|     "@babel/core": "^7.12.10", | ||||
|     "@babel/core": "^7.12.16", | ||||
|     "@babel/plugin-proposal-class-properties": "^7.8.3", | ||||
|     "@babel/plugin-proposal-decorators": "^7.12.12", | ||||
|     "@babel/plugin-transform-react-inline-elements": "^7.12.1", | ||||
|     "@babel/plugin-transform-runtime": "^7.12.10", | ||||
|     "@babel/preset-env": "^7.12.11", | ||||
|     "@babel/preset-react": "^7.12.10", | ||||
|     "@babel/runtime": "^7.12.5", | ||||
|     "@babel/plugin-proposal-decorators": "^7.12.13", | ||||
|     "@babel/plugin-transform-react-inline-elements": "^7.12.13", | ||||
|     "@babel/plugin-transform-runtime": "^7.12.15", | ||||
|     "@babel/preset-env": "^7.12.16", | ||||
|     "@babel/preset-react": "^7.12.13", | ||||
|     "@babel/runtime": "^7.12.13", | ||||
|     "@clusterws/cws": "^3.0.0", | ||||
|     "@gamestdio/websocket": "^0.3.2", | ||||
|     "@github/webauthn-json": "^0.5.7", | ||||
|     "@rails/ujs": "^6.1.1", | ||||
|     "@rails/ujs": "^6.1.2", | ||||
|     "array-includes": "^3.1.2", | ||||
|     "atrament": "0.2.4", | ||||
|     "arrow-key-navigation": "^1.2.0", | ||||
|  | @ -88,7 +88,7 @@ | |||
|     "color-blend": "^3.0.1", | ||||
|     "compression-webpack-plugin": "^6.1.1", | ||||
|     "cross-env": "^7.0.3", | ||||
|     "css-loader": "^5.0.1", | ||||
|     "css-loader": "^5.0.2", | ||||
|     "cssnano": "^4.1.10", | ||||
|     "detect-passive-events": "^2.0.2", | ||||
|     "dotenv": "^8.2.0", | ||||
|  | @ -114,7 +114,7 @@ | |||
|     "lodash": "^4.17.19", | ||||
|     "mark-loader": "^0.1.6", | ||||
|     "marky": "^1.2.1", | ||||
|     "mini-css-extract-plugin": "^1.3.5", | ||||
|     "mini-css-extract-plugin": "^1.3.6", | ||||
|     "mkdirp": "^1.0.4", | ||||
|     "npmlog": "^4.1.2", | ||||
|     "object-assign": "^4.1.1", | ||||
|  | @ -145,7 +145,7 @@ | |||
|     "react-select": "^3.2.0", | ||||
|     "react-sparklines": "^1.7.0", | ||||
|     "react-swipeable-views": "^0.13.9", | ||||
|     "react-textarea-autosize": "^8.3.0", | ||||
|     "react-textarea-autosize": "^8.3.1", | ||||
|     "react-toggle": "^4.1.1", | ||||
|     "redis": "^3.0.2", | ||||
|     "redux": "^4.0.5", | ||||
|  | @ -156,7 +156,7 @@ | |||
|     "requestidlecallback": "^0.3.0", | ||||
|     "reselect": "^4.0.0", | ||||
|     "rimraf": "^3.0.2", | ||||
|     "sass": "^1.32.5", | ||||
|     "sass": "^1.32.7", | ||||
|     "sass-loader": "^10.1.1", | ||||
|     "stacktrace-js": "^2.0.2", | ||||
|     "stringz": "^2.1.0", | ||||
|  | @ -175,13 +175,13 @@ | |||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@testing-library/jest-dom": "^5.11.9", | ||||
|     "@testing-library/react": "^11.2.3", | ||||
|     "@testing-library/react": "^11.2.5", | ||||
|     "babel-eslint": "^10.1.0", | ||||
|     "babel-jest": "^26.6.3", | ||||
|     "eslint": "^7.18.0", | ||||
|     "eslint": "^7.19.0", | ||||
|     "eslint-plugin-import": "~2.22.1", | ||||
|     "eslint-plugin-jsx-a11y": "~6.4.1", | ||||
|     "eslint-plugin-promise": "~4.2.1", | ||||
|     "eslint-plugin-promise": "~4.3.1", | ||||
|     "eslint-plugin-react": "~7.22.0", | ||||
|     "jest": "^26.6.3", | ||||
|     "raf": "^3.4.1", | ||||
|  |  | |||
|  | @ -17,62 +17,171 @@ RSpec.describe ActivityPub::Activity::Follow do | |||
|   describe '#perform' do | ||||
|     subject { described_class.new(json, sender) } | ||||
| 
 | ||||
|     context 'unlocked account' do | ||||
|       before do | ||||
|         subject.perform | ||||
|     context 'with no prior follow' do | ||||
|       context 'unlocked account' do | ||||
|         before do | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'creates a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be true | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'creates a follow from sender to recipient' do | ||||
|         expect(sender.following?(recipient)).to be true | ||||
|       context 'silenced account following an unlocked account' do | ||||
|         before do | ||||
|           sender.touch(:silenced_at) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be false | ||||
|         end | ||||
| 
 | ||||
|         it 'creates a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be true | ||||
|           expect(sender.follow_requests.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'does not create a follow request' do | ||||
|         expect(sender.requested?(recipient)).to be false | ||||
|       context 'unlocked account muting the sender' do | ||||
|         before do | ||||
|           recipient.mute!(sender) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'creates a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be true | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'locked account' do | ||||
|         before do | ||||
|           recipient.update(locked: true) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be false | ||||
|         end | ||||
| 
 | ||||
|         it 'creates a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be true | ||||
|           expect(sender.follow_requests.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'silenced account following an unlocked account' do | ||||
|     context 'when a follow relationship already exists' do | ||||
|       before do | ||||
|         sender.touch(:silenced_at) | ||||
|         subject.perform | ||||
|         sender.active_relationships.create!(target_account: recipient, uri: 'bar') | ||||
|       end | ||||
| 
 | ||||
|       it 'does not create a follow from sender to recipient' do | ||||
|         expect(sender.following?(recipient)).to be false | ||||
|       context 'unlocked account' do | ||||
|         before do | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'creates a follow request' do | ||||
|         expect(sender.requested?(recipient)).to be true | ||||
|       context 'silenced account following an unlocked account' do | ||||
|         before do | ||||
|           sender.touch(:silenced_at) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'unlocked account muting the sender' do | ||||
|         before do | ||||
|           recipient.mute!(sender) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       context 'locked account' do | ||||
|         before do | ||||
|           recipient.update(locked: true) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.active_relationships.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow request' do | ||||
|           expect(sender.requested?(recipient)).to be false | ||||
|         end | ||||
|       end | ||||
|     end | ||||
| 
 | ||||
|     context 'unlocked account muting the sender' do | ||||
|     context 'when a follow request already exists' do | ||||
|       before do | ||||
|         recipient.mute!(sender) | ||||
|         subject.perform | ||||
|         sender.follow_requests.create!(target_account: recipient, uri: 'bar') | ||||
|       end | ||||
| 
 | ||||
|       it 'creates a follow from sender to recipient' do | ||||
|         expect(sender.following?(recipient)).to be true | ||||
|       context 'silenced account following an unlocked account' do | ||||
|         before do | ||||
|           sender.touch(:silenced_at) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|         it 'does not create a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be false | ||||
|         end | ||||
| 
 | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.requested?(recipient)).to be true | ||||
|           expect(sender.follow_requests.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
|       end | ||||
| 
 | ||||
|       it 'does not create a follow request' do | ||||
|         expect(sender.requested?(recipient)).to be false | ||||
|       end | ||||
|     end | ||||
|       context 'locked account' do | ||||
|         before do | ||||
|           recipient.update(locked: true) | ||||
|           subject.perform | ||||
|         end | ||||
| 
 | ||||
|     context 'locked account' do | ||||
|       before do | ||||
|         recipient.update(locked: true) | ||||
|         subject.perform | ||||
|       end | ||||
|         it 'does not create a follow from sender to recipient' do | ||||
|           expect(sender.following?(recipient)).to be false | ||||
|         end | ||||
| 
 | ||||
|       it 'does not create a follow from sender to recipient' do | ||||
|         expect(sender.following?(recipient)).to be false | ||||
|       end | ||||
| 
 | ||||
|       it 'creates a follow request' do | ||||
|         expect(sender.requested?(recipient)).to be true | ||||
|         it 'correctly sets the new URI' do | ||||
|           expect(sender.requested?(recipient)).to be true | ||||
|           expect(sender.follow_requests.find_by(target_account: recipient).uri).to eq 'foo' | ||||
|         end | ||||
|       end | ||||
|     end | ||||
|   end | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue