diff --git a/Gemfile b/Gemfile
index 32ebf0dd1e1..f9eddd932a6 100644
--- a/Gemfile
+++ b/Gemfile
@@ -82,6 +82,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
gem 'rqrcode', '~> 1.1'
gem 'ruby-progressbar', '~> 1.10'
gem 'sanitize', '~> 5.2'
+gem 'scenic', '~> 1.5'
gem 'sidekiq', '~> 6.1'
gem 'sidekiq-scheduler', '~> 3.0'
gem 'sidekiq-unique-jobs', '~> 6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index f5d8d20c1f0..a356029a6ee 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -562,6 +562,9 @@ GEM
crass (~> 1.0.2)
nokogiri (>= 1.8.0)
nokogumbo (~> 2.0)
+ scenic (1.5.4)
+ activerecord (>= 4.0.0)
+ railties (>= 4.0.0)
securecompare (1.0.0)
semantic_range (2.3.0)
sidekiq (6.1.2)
@@ -784,6 +787,7 @@ DEPENDENCIES
rubocop-rails (~> 2.8)
ruby-progressbar (~> 1.10)
sanitize (~> 5.2)
+ scenic (~> 1.5)
sidekiq (~> 6.1)
sidekiq-bulk (~> 0.2.0)
sidekiq-scheduler (~> 3.0)
diff --git a/app/controllers/admin/domain_blocks_controller.rb b/app/controllers/admin/domain_blocks_controller.rb
index 74a36b79ca3..6a5b41a7471 100644
--- a/app/controllers/admin/domain_blocks_controller.rb
+++ b/app/controllers/admin/domain_blocks_controller.rb
@@ -29,6 +29,7 @@ module Admin
@domain_block = existing_domain_block
@domain_block.update(resource_params)
end
+
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id)
log_action :create, @domain_block
@@ -40,7 +41,7 @@ module Admin
end
def update
- authorize :domain_block, :create?
+ authorize :domain_block, :update?
@domain_block.update(update_params)
@@ -48,7 +49,7 @@ module Admin
if @domain_block.save
DomainBlockWorker.perform_async(@domain_block.id, severity_changed)
- log_action :create, @domain_block
+ log_action :update, @domain_block
redirect_to admin_instances_path(limited: '1'), notice: I18n.t('admin.domain_blocks.created_msg')
else
render :edit
diff --git a/app/controllers/admin/instances_controller.rb b/app/controllers/admin/instances_controller.rb
index 1790becbf2f..b5918d231c7 100644
--- a/app/controllers/admin/instances_controller.rb
+++ b/app/controllers/admin/instances_controller.rb
@@ -2,65 +2,31 @@
module Admin
class InstancesController < BaseController
- before_action :set_domain_block, only: :show
- before_action :set_domain_allow, only: :show
+ before_action :set_instances, only: :index
before_action :set_instance, only: :show
def index
authorize :instance, :index?
-
- @instances = ordered_instances
end
def show
authorize :instance, :show?
-
- @following_count = Follow.where(account: Account.where(domain: params[:id])).count
- @followers_count = Follow.where(target_account: Account.where(domain: params[:id])).count
- @reports_count = Report.where(target_account: Account.where(domain: params[:id])).count
- @blocks_count = Block.where(target_account: Account.where(domain: params[:id])).count
- @available = DeliveryFailureTracker.available?(params[:id])
- @media_storage = MediaAttachment.where(account: Account.where(domain: params[:id])).sum(:file_file_size)
- @private_comment = @domain_block&.private_comment
- @public_comment = @domain_block&.public_comment
end
private
- def set_domain_block
- @domain_block = DomainBlock.rule_for(params[:id])
- end
-
- def set_domain_allow
- @domain_allow = DomainAllow.rule_for(params[:id])
- end
-
def set_instance
- resource = Account.by_domain_accounts.find_by(domain: params[:id])
- resource ||= @domain_block
- resource ||= @domain_allow
+ @instance = Instance.find(params[:id])
+ end
- if resource
- @instance = Instance.new(resource)
- else
- not_found
- end
+ def set_instances
+ @instances = filtered_instances.page(params[:page])
end
def filtered_instances
InstanceFilter.new(whitelist_mode? ? { allowed: true } : filter_params).results
end
- def paginated_instances
- filtered_instances.page(params[:page])
- end
-
- helper_method :paginated_instances
-
- def ordered_instances
- paginated_instances.map { |resource| Instance.new(resource) }
- end
-
def filter_params
params.slice(*InstanceFilter::KEYS).permit(*InstanceFilter::KEYS)
end
diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb
index fe199e689a0..85f4cc7681a 100644
--- a/app/controllers/api/base_controller.rb
+++ b/app/controllers/api/base_controller.rb
@@ -40,7 +40,7 @@ class Api::BaseController < ApplicationController
render json: { error: 'This action is not allowed' }, status: 403
end
- rescue_from Mastodon::RaceConditionError do
+ rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight do
render json: { error: 'There was a temporary problem serving your request, please try again' }, status: 503
end
diff --git a/app/controllers/api/v1/instances/peers_controller.rb b/app/controllers/api/v1/instances/peers_controller.rb
index 9fa4409357b..2877fec52d8 100644
--- a/app/controllers/api/v1/instances/peers_controller.rb
+++ b/app/controllers/api/v1/instances/peers_controller.rb
@@ -8,7 +8,7 @@ class Api::V1::Instances::PeersController < Api::BaseController
def index
expires_in 1.day, public: true
- render_with_cache(expires_in: 1.day) { Account.remote.domains }
+ render_with_cache(expires_in: 1.day) { Instance.where.not(domain: DomainBlock.select(:domain)).pluck(:domain) }
end
private
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e996c2217fa..41fe9d88a42 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from Mastodon::NotPermittedError, with: :forbidden
rescue_from HTTP::Error, OpenSSL::SSL::SSLError, with: :internal_server_error
- rescue_from Mastodon::RaceConditionError, with: :service_unavailable
+ rescue_from Mastodon::RaceConditionError, Seahorse::Client::NetworkingError, Stoplight::Error::RedLight, with: :service_unavailable
rescue_from Mastodon::RateLimitExceededError, with: :too_many_requests
before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
diff --git a/app/helpers/statuses_helper.rb b/app/helpers/statuses_helper.rb
index daed9048f45..1f654f34fc9 100644
--- a/app/helpers/statuses_helper.rb
+++ b/app/helpers/statuses_helper.rb
@@ -92,22 +92,6 @@ module StatusesHelper
end
end
- def rtl_status?(status)
- status.local? ? rtl?(status.text) : rtl?(strip_tags(status.text))
- end
-
- def rtl?(text)
- text = simplified_text(text)
- rtl_words = text.scan(/[\p{Hebrew}\p{Arabic}\p{Syriac}\p{Thaana}\p{Nko}]+/m)
-
- if rtl_words.present?
- total_size = text.size.to_f
- rtl_size(rtl_words) / total_size > 0.3
- else
- false
- end
- end
-
def fa_visibility_icon(status)
case status.visibility
when 'public'
@@ -143,10 +127,6 @@ module StatusesHelper
end
end
- def rtl_size(words)
- words.reduce(0) { |acc, elem| acc + elem.size }.to_f
- end
-
def embedded_view?
params[:controller] == EMBEDDED_CONTROLLER && params[:action] == EMBEDDED_ACTION
end
diff --git a/app/javascript/core/admin.js b/app/javascript/core/admin.js
index bbc7cfac79d..d2db89ca77f 100644
--- a/app/javascript/core/admin.js
+++ b/app/javascript/core/admin.js
@@ -59,18 +59,46 @@ const onEnableBootstrapTimelineAccountsChange = (target) => {
bootstrapTimelineAccountsField.disabled = !target.checked;
if (target.checked) {
bootstrapTimelineAccountsField.parentElement.classList.remove('disabled');
+ bootstrapTimelineAccountsField.parentElement.parentElement.classList.remove('disabled');
} else {
bootstrapTimelineAccountsField.parentElement.classList.add('disabled');
+ bootstrapTimelineAccountsField.parentElement.parentElement.classList.add('disabled');
}
}
};
delegate(document, '#form_admin_settings_enable_bootstrap_timeline_accounts', 'change', ({ target }) => onEnableBootstrapTimelineAccountsChange(target));
+const onChangeRegistrationMode = (target) => {
+ const enabled = target.value === 'approved';
+
+ [].forEach.call(document.querySelectorAll('#form_admin_settings_require_invite_text'), (input) => {
+ input.disabled = !enabled;
+ if (enabled) {
+ let element = input;
+ do {
+ element.classList.remove('disabled');
+ element = element.parentElement;
+ } while (element && !element.classList.contains('fields-group'));
+ } else {
+ let element = input;
+ do {
+ element.classList.add('disabled');
+ element = element.parentElement;
+ } while (element && !element.classList.contains('fields-group'));
+ }
+ });
+};
+
+delegate(document, '#form_admin_settings_registrations_mode', 'change', ({ target }) => onChangeRegistrationMode(target));
+
ready(() => {
const domainBlockSeverityInput = document.getElementById('domain_block_severity');
if (domainBlockSeverityInput) onDomainBlockSeverityChange(domainBlockSeverityInput);
const enableBootstrapTimelineAccounts = document.getElementById('form_admin_settings_enable_bootstrap_timeline_accounts');
if (enableBootstrapTimelineAccounts) onEnableBootstrapTimelineAccountsChange(enableBootstrapTimelineAccounts);
+
+ const registrationMode = document.getElementById('form_admin_settings_registrations_mode');
+ if (registrationMode) onChangeRegistrationMode(registrationMode);
});
diff --git a/app/javascript/mastodon/components/autosuggest_input.js b/app/javascript/mastodon/components/autosuggest_input.js
index 6d2035add01..5187f95c847 100644
--- a/app/javascript/mastodon/components/autosuggest_input.js
+++ b/app/javascript/mastodon/components/autosuggest_input.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
import AutosuggestHashtag from './autosuggest_hashtag';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import classNames from 'classnames';
import { List as ImmutableList } from 'immutable';
@@ -189,11 +188,6 @@ export default class AutosuggestInput extends ImmutablePureComponent {
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, className, id, maxLength } = this.props;
const { suggestionsHidden } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
- }
return (
@@ -212,7 +206,7 @@ export default class AutosuggestInput extends ImmutablePureComponent {
onKeyUp={onKeyUp}
onFocus={this.onFocus}
onBlur={this.onBlur}
- style={style}
+ dir='auto'
aria-autocomplete='list'
id={id}
className={className}
diff --git a/app/javascript/mastodon/components/autosuggest_textarea.js b/app/javascript/mastodon/components/autosuggest_textarea.js
index 58ec4f6eb66..08b9cd80bbf 100644
--- a/app/javascript/mastodon/components/autosuggest_textarea.js
+++ b/app/javascript/mastodon/components/autosuggest_textarea.js
@@ -4,7 +4,6 @@ import AutosuggestEmoji from './autosuggest_emoji';
import AutosuggestHashtag from './autosuggest_hashtag';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import Textarea from 'react-textarea-autosize';
import classNames from 'classnames';
@@ -195,11 +194,6 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
render () {
const { value, suggestions, disabled, placeholder, onKeyUp, autoFocus, children } = this.props;
const { suggestionsHidden } = this.state;
- const style = { direction: 'ltr' };
-
- if (isRtl(value)) {
- style.direction = 'rtl';
- }
return [
@@ -220,7 +214,7 @@ export default class AutosuggestTextarea extends ImmutablePureComponent {
onFocus={this.onFocus}
onBlur={this.onBlur}
onPaste={this.onPaste}
- style={style}
+ dir='auto'
aria-autocomplete='list'
/>
diff --git a/app/javascript/mastodon/components/status_content.js b/app/javascript/mastodon/components/status_content.js
index 3200f2d82f6..185a2a663f9 100644
--- a/app/javascript/mastodon/components/status_content.js
+++ b/app/javascript/mastodon/components/status_content.js
@@ -1,7 +1,6 @@
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import PropTypes from 'prop-types';
-import { isRtl } from '../rtl';
import { FormattedMessage } from 'react-intl';
import Permalink from './permalink';
import classnames from 'classnames';
@@ -186,17 +185,12 @@ export default class StatusContent extends React.PureComponent {
const content = { __html: status.get('contentHtml') };
const spoilerContent = { __html: status.get('spoilerHtml') };
- const directionStyle = { direction: 'ltr' };
const classNames = classnames('status__content', {
'status__content--with-action': this.props.onClick && this.context.router,
'status__content--with-spoiler': status.get('spoiler_text').length > 0,
'status__content--collapsed': renderReadMore,
});
- if (isRtl(status.get('search_index'))) {
- directionStyle.direction = 'rtl';
- }
-
const showThreadButton = (