diff --git a/.codeclimate.yml b/.codeclimate.yml
index 62dc2334ca..8701e5f3d2 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -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:
diff --git a/Gemfile b/Gemfile
index 639a477298..fc38339c96 100644
--- a/Gemfile
+++ b/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
diff --git a/Gemfile.lock b/Gemfile.lock
index 7d1a467dad..aa8cba1f6f 100644
--- a/Gemfile.lock
+++ b/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)
diff --git a/Vagrantfile b/Vagrantfile
index 143fa27fdb..addc6b0f4a 100644
--- a/Vagrantfile
+++ b/Vagrantfile
@@ -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
diff --git a/app/controllers/api/web/settings_controller.rb b/app/controllers/api/web/settings_controller.rb
index 3d65e46ed9..601e25e6ee 100644
--- a/app/controllers/api/web/settings_controller.rb
+++ b/app/controllers/api/web/settings_controller.rb
@@ -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
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 41fe9d88a4..a4b740b893 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -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?
diff --git a/app/controllers/instance_actors_controller.rb b/app/controllers/instance_actors_controller.rb
index 4b074ca192..b3b5476e2f 100644
--- a/app/controllers/instance_actors_controller.rb
+++ b/app/controllers/instance_actors_controller.rb
@@ -13,7 +13,7 @@ class InstanceActorsController < ApplicationController
private
def set_account
- @account = Account.find(-99)
+ @account = Account.representative
end
def restrict_fields_to
diff --git a/app/helpers/mascot_helper.rb b/app/helpers/mascot_helper.rb
new file mode 100644
index 0000000000..0124c74f19
--- /dev/null
+++ b/app/helpers/mascot_helper.rb
@@ -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
diff --git a/app/javascript/flavours/glitch/actions/boosts.js b/app/javascript/flavours/glitch/actions/boosts.js
new file mode 100644
index 0000000000..6e14065d6f
--- /dev/null
+++ b/app/javascript/flavours/glitch/actions/boosts.js
@@ -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,
+ });
+ };
+}
diff --git a/app/javascript/flavours/glitch/actions/interactions.js b/app/javascript/flavours/glitch/actions/interactions.js
index 4407f8b6ea..336c8fa907 100644
--- a/app/javascript/flavours/glitch/actions/interactions.js
+++ b/app/javascript/flavours/glitch/actions/interactions.js
@@ -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));
diff --git a/app/javascript/flavours/glitch/components/dropdown_menu.js b/app/javascript/flavours/glitch/components/dropdown_menu.js
index d1aba691ca..023fecb9ab 100644
--- a/app/javascript/flavours/glitch/components/dropdown_menu.js
+++ b/app/javascript/flavours/glitch/components/dropdown_menu.js
@@ -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,
diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js
index 2ccb02c624..74bfd948ed 100644
--- a/app/javascript/flavours/glitch/components/status_action_bar.js
+++ b/app/javascript/flavours/glitch/components/status_action_bar.js
@@ -193,9 +193,10 @@ class StatusActionBar extends ImmutablePureComponent {
render () {
const { status, intl, withDismiss, showReplyCount, directMessage, scrollKey } = this.props;
- const mutingConversation = status.get('muted');
const anonymousAccess = !me;
+ const mutingConversation = status.get('muted');
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'));
+ const writtenByMe = status.getIn(['account', 'id']) === me;
let menu = [];
let reblogIcon = 'retweet';
@@ -211,16 +212,17 @@ class StatusActionBar extends ImmutablePureComponent {
menu.push(null);
- if (status.getIn(['account', 'id']) === me || withDismiss) {
+ 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 {
diff --git a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js
index e60ee814d8..1c0385b740 100644
--- a/app/javascript/flavours/glitch/containers/dropdown_menu_container.js
+++ b/app/javascript/flavours/glitch/containers/dropdown_menu_container.js
@@ -5,7 +5,6 @@ import DropdownMenu from 'flavours/glitch/components/dropdown_menu';
import { isUserTouching } from 'flavours/glitch/util/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']),
diff --git a/app/javascript/flavours/glitch/containers/status_container.js b/app/javascript/flavours/glitch/containers/status_container.js
index 7782246a60..6461bf8050 100644
--- a/app/javascript/flavours/glitch/containers/status_container.js
+++ b/app/javascript/flavours/glitch/containers/status_container.js
@@ -21,6 +21,7 @@ import { muteStatus, unmuteStatus, deleteStatus } from 'flavours/glitch/actions/
import { initMuteModal } from 'flavours/glitch/actions/mutes';
import { initBlockModal } from 'flavours/glitch/actions/blocks';
import { initReport } from 'flavours/glitch/actions/reports';
+import { initBoostModal } from 'flavours/glitch/actions/boosts';
import { openModal } from 'flavours/glitch/actions/modal';
import { deployPictureInPicture } from 'flavours/glitch/actions/picture_in_picture';
import { changeLocalSetting } from 'flavours/glitch/actions/local_settings';
@@ -96,11 +97,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
});
},
- onModalReblog (status) {
+ onModalReblog (status, privacy) {
if (status.get('reblogged')) {
dispatch(unreblog(status));
} else {
- dispatch(reblog(status));
+ dispatch(reblog(status, privacy));
}
},
@@ -108,11 +109,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['local_settings', 'confirm_boost_missing_media_description']) && status.get('media_attachments').some(item => !item.get('description')) && !status.get('reblogged')) {
- dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog, missingMediaDescription: true }));
+ dispatch(initBoostModal({ status, onReblog: this.onModalReblog, missingMediaDescription: true }));
} else if (e.shiftKey || !boostModal) {
this.onModalReblog(status);
} else {
- dispatch(openModal('BOOST', { status, onReblog: this.onModalReblog }));
+ dispatch(initBoostModal({ status, onReblog: this.onModalReblog }));
}
});
},
diff --git a/app/javascript/flavours/glitch/features/compose/components/dropdown.js b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
index 04ef3964b2..abf7cbba14 100644
--- a/app/javascript/flavours/glitch/features/compose/components/dropdown.js
+++ b/app/javascript/flavours/glitch/features/compose/components/dropdown.js
@@ -31,6 +31,8 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
title: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func,
+ noModal: PropTypes.bool,
+ container: PropTypes.func,
};
state = {
@@ -42,10 +44,10 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
// Toggles opening and closing the dropdown.
handleToggle = ({ target, type }) => {
- const { onModalOpen } = this.props;
+ const { onModalOpen, noModal } = this.props;
const { open } = this.state;
- if (isUserTouching()) {
+ if (!noModal && isUserTouching()) {
if (this.state.open) {
this.props.onModalClose();
} else {
@@ -183,6 +185,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
items,
onChange,
value,
+ container,
} = this.props;
const { open, placement } = this.state;
const computedClass = classNames('composer--options--dropdown', {
@@ -219,6 +222,7 @@ export default class ComposerOptionsDropdown extends React.PureComponent {
placement={placement}
show={open}
target={this}
+ container={container}
>