mirror of
https://github.com/mastodon/mastodon.git
synced 2024-08-20 21:08:15 -07:00
Compare commits
No commits in common. "aa98c8fbeb02fecac2681464fd7c0445deb466b1" and "af578e8ce0aabdbe9c0cd3d72d6fa2cc30b7fc66" have entirely different histories.
aa98c8fbeb
...
af578e8ce0
42 changed files with 266 additions and 594 deletions
|
@ -97,10 +97,6 @@ Rails/Exit:
|
||||||
- 'lib/mastodon/cli_helper.rb'
|
- 'lib/mastodon/cli_helper.rb'
|
||||||
- 'lib/cli.rb'
|
- 'lib/cli.rb'
|
||||||
|
|
||||||
RSpec/FilePath:
|
|
||||||
CustomTransform:
|
|
||||||
DeepL: deepl
|
|
||||||
|
|
||||||
RSpec/NotToNot:
|
RSpec/NotToNot:
|
||||||
EnforcedStyle: to_not
|
EnforcedStyle: to_not
|
||||||
|
|
||||||
|
@ -127,6 +123,3 @@ Style/TrailingCommaInArrayLiteral:
|
||||||
|
|
||||||
Style/TrailingCommaInHashLiteral:
|
Style/TrailingCommaInHashLiteral:
|
||||||
EnforcedStyleForMultiline: 'comma'
|
EnforcedStyleForMultiline: 'comma'
|
||||||
|
|
||||||
Style/SymbolArray:
|
|
||||||
Enabled: false
|
|
||||||
|
|
|
@ -2235,3 +2235,134 @@ Style/SlicingWithRange:
|
||||||
- 'lib/active_record/batches.rb'
|
- 'lib/active_record/batches.rb'
|
||||||
- 'lib/mastodon/premailer_webpack_strategy.rb'
|
- 'lib/mastodon/premailer_webpack_strategy.rb'
|
||||||
- 'lib/tasks/repo.rake'
|
- 'lib/tasks/repo.rake'
|
||||||
|
|
||||||
|
# Offense count: 272
|
||||||
|
# This cop supports safe autocorrection (--autocorrect).
|
||||||
|
# Configuration parameters: EnforcedStyle, MinSize.
|
||||||
|
# SupportedStyles: percent, brackets
|
||||||
|
Style/SymbolArray:
|
||||||
|
Exclude:
|
||||||
|
- 'app/controllers/accounts_controller.rb'
|
||||||
|
- 'app/controllers/activitypub/replies_controller.rb'
|
||||||
|
- 'app/controllers/admin/accounts_controller.rb'
|
||||||
|
- 'app/controllers/admin/announcements_controller.rb'
|
||||||
|
- 'app/controllers/admin/domain_blocks_controller.rb'
|
||||||
|
- 'app/controllers/admin/email_domain_blocks_controller.rb'
|
||||||
|
- 'app/controllers/admin/relationships_controller.rb'
|
||||||
|
- 'app/controllers/admin/relays_controller.rb'
|
||||||
|
- 'app/controllers/admin/roles_controller.rb'
|
||||||
|
- 'app/controllers/admin/rules_controller.rb'
|
||||||
|
- 'app/controllers/admin/statuses_controller.rb'
|
||||||
|
- 'app/controllers/admin/trends/statuses_controller.rb'
|
||||||
|
- 'app/controllers/admin/warning_presets_controller.rb'
|
||||||
|
- 'app/controllers/admin/webhooks_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/accounts/credentials_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/accounts_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/accounts_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/canonical_email_blocks_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/domain_allows_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/domain_blocks_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/email_domain_blocks_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/ip_blocks_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/admin/reports_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/crypto/deliveries_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/crypto/keys/claims_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/crypto/keys/uploads_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/featured_tags_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/filters_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/lists_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/notifications_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/push/subscriptions_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/scheduled_statuses_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb'
|
||||||
|
- 'app/controllers/api/v1/statuses_controller.rb'
|
||||||
|
- 'app/controllers/api/v2/filters/keywords_controller.rb'
|
||||||
|
- 'app/controllers/api/v2/filters/statuses_controller.rb'
|
||||||
|
- 'app/controllers/api/v2/filters_controller.rb'
|
||||||
|
- 'app/controllers/api/web/push_subscriptions_controller.rb'
|
||||||
|
- 'app/controllers/application_controller.rb'
|
||||||
|
- 'app/controllers/auth/registrations_controller.rb'
|
||||||
|
- 'app/controllers/filters_controller.rb'
|
||||||
|
- 'app/controllers/settings/applications_controller.rb'
|
||||||
|
- 'app/controllers/settings/featured_tags_controller.rb'
|
||||||
|
- 'app/controllers/settings/profiles_controller.rb'
|
||||||
|
- 'app/controllers/settings/two_factor_authentication/webauthn_credentials_controller.rb'
|
||||||
|
- 'app/controllers/statuses_controller.rb'
|
||||||
|
- 'app/lib/feed_manager.rb'
|
||||||
|
- 'app/models/account.rb'
|
||||||
|
- 'app/models/account_filter.rb'
|
||||||
|
- 'app/models/admin/status_filter.rb'
|
||||||
|
- 'app/models/announcement.rb'
|
||||||
|
- 'app/models/concerns/ldap_authenticable.rb'
|
||||||
|
- 'app/models/concerns/status_threading_concern.rb'
|
||||||
|
- 'app/models/custom_filter.rb'
|
||||||
|
- 'app/models/domain_block.rb'
|
||||||
|
- 'app/models/import.rb'
|
||||||
|
- 'app/models/list.rb'
|
||||||
|
- 'app/models/media_attachment.rb'
|
||||||
|
- 'app/models/preview_card.rb'
|
||||||
|
- 'app/models/relay.rb'
|
||||||
|
- 'app/models/report.rb'
|
||||||
|
- 'app/models/site_upload.rb'
|
||||||
|
- 'app/models/status.rb'
|
||||||
|
- 'app/serializers/initial_state_serializer.rb'
|
||||||
|
- 'app/serializers/rest/notification_serializer.rb'
|
||||||
|
- 'db/migrate/20160220174730_create_accounts.rb'
|
||||||
|
- 'db/migrate/20160221003621_create_follows.rb'
|
||||||
|
- 'db/migrate/20160223171800_create_favourites.rb'
|
||||||
|
- 'db/migrate/20160224223247_create_mentions.rb'
|
||||||
|
- 'db/migrate/20160314164231_add_owner_to_application.rb'
|
||||||
|
- 'db/migrate/20160316103650_add_missing_indices.rb'
|
||||||
|
- 'db/migrate/20160926213048_remove_owner_from_application.rb'
|
||||||
|
- 'db/migrate/20161003145426_create_blocks.rb'
|
||||||
|
- 'db/migrate/20161006213403_rails_settings_migration.rb'
|
||||||
|
- 'db/migrate/20161105130633_create_statuses_tags_join_table.rb'
|
||||||
|
- 'db/migrate/20161119211120_create_notifications.rb'
|
||||||
|
- 'db/migrate/20161128103007_create_subscriptions.rb'
|
||||||
|
- 'db/migrate/20161222204147_create_follow_requests.rb'
|
||||||
|
- 'db/migrate/20170112154826_migrate_settings.rb'
|
||||||
|
- 'db/migrate/20170301222600_create_mutes.rb'
|
||||||
|
- 'db/migrate/20170406215816_add_notifications_and_favourites_indices.rb'
|
||||||
|
- 'db/migrate/20170424003227_create_account_domain_blocks.rb'
|
||||||
|
- 'db/migrate/20170427011934_re_add_owner_to_application.rb'
|
||||||
|
- 'db/migrate/20170507141759_optimize_index_subscriptions.rb'
|
||||||
|
- 'db/migrate/20170508230434_create_conversation_mutes.rb'
|
||||||
|
- 'db/migrate/20170720000000_add_index_favourites_on_account_id_and_id.rb'
|
||||||
|
- 'db/migrate/20170823162448_create_status_pins.rb'
|
||||||
|
- 'db/migrate/20170901142658_create_join_table_preview_cards_statuses.rb'
|
||||||
|
- 'db/migrate/20170905044538_add_index_id_account_id_activity_type_on_notifications.rb'
|
||||||
|
- 'db/migrate/20170917153509_create_custom_emojis.rb'
|
||||||
|
- 'db/migrate/20170918125918_ids_to_bigints.rb'
|
||||||
|
- 'db/migrate/20171116161857_create_list_accounts.rb'
|
||||||
|
- 'db/migrate/20171122120436_add_index_account_and_reblog_of_id_to_statuses.rb'
|
||||||
|
- 'db/migrate/20171125185353_add_index_reblog_of_id_and_account_to_statuses.rb'
|
||||||
|
- 'db/migrate/20171125190735_remove_old_reblog_index_on_statuses.rb'
|
||||||
|
- 'db/migrate/20171129172043_add_index_on_stream_entries.rb'
|
||||||
|
- 'db/migrate/20171226094803_more_faster_index_on_notifications.rb'
|
||||||
|
- 'db/migrate/20180106000232_add_index_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
|
||||||
|
- 'db/migrate/20180514140000_revert_index_change_on_statuses_for_api_v1_accounts_account_id_statuses.rb'
|
||||||
|
- 'db/migrate/20180808175627_create_account_pins.rb'
|
||||||
|
- 'db/migrate/20180831171112_create_bookmarks.rb'
|
||||||
|
- 'db/migrate/20180929222014_create_account_conversations.rb'
|
||||||
|
- 'db/migrate/20181007025445_create_pghero_space_stats.rb'
|
||||||
|
- 'db/migrate/20181203003808_create_accounts_tags_join_table.rb'
|
||||||
|
- 'db/migrate/20190316190352_create_account_identity_proofs.rb'
|
||||||
|
- 'db/migrate/20190511134027_add_silenced_at_suspended_at_to_accounts.rb'
|
||||||
|
- 'db/migrate/20190820003045_update_statuses_index.rb'
|
||||||
|
- 'db/migrate/20190823221802_add_local_index_to_statuses.rb'
|
||||||
|
- 'db/migrate/20190904222339_create_markers.rb'
|
||||||
|
- 'db/migrate/20200113125135_create_announcement_mutes.rb'
|
||||||
|
- 'db/migrate/20200114113335_create_announcement_reactions.rb'
|
||||||
|
- 'db/migrate/20200119112504_add_public_index_to_statuses.rb'
|
||||||
|
- 'db/migrate/20200628133322_create_account_notes.rb'
|
||||||
|
- 'db/migrate/20200917222316_add_index_notifications_on_type.rb'
|
||||||
|
- 'db/migrate/20210425135952_add_index_on_media_attachments_account_id_status_id.rb'
|
||||||
|
- 'db/migrate/20220714171049_create_tag_follows.rb'
|
||||||
|
- 'db/migrate/20221021055441_add_index_featured_tags_on_account_id_and_tag_id.rb'
|
||||||
|
- 'db/post_migrate/20190511152737_remove_suspended_silenced_account_fields.rb'
|
||||||
|
- 'db/post_migrate/20200917222734_remove_index_notifications_on_account_activity.rb'
|
||||||
|
- 'spec/controllers/api/v1/streaming_controller_spec.rb'
|
||||||
|
- 'spec/controllers/api/v2/admin/accounts_controller_spec.rb'
|
||||||
|
- 'spec/controllers/concerns/signature_verification_spec.rb'
|
||||||
|
- 'spec/fabricators/notification_fabricator.rb'
|
||||||
|
- 'spec/models/public_feed_spec.rb'
|
||||||
|
|
3
Gemfile
3
Gemfile
|
@ -104,6 +104,8 @@ group :development, :test do
|
||||||
gem 'fabrication', '~> 2.30'
|
gem 'fabrication', '~> 2.30'
|
||||||
gem 'fuubar', '~> 2.5'
|
gem 'fuubar', '~> 2.5'
|
||||||
gem 'i18n-tasks', '~> 1.0', require: false
|
gem 'i18n-tasks', '~> 1.0', require: false
|
||||||
|
gem 'pry-byebug', '~> 3.10'
|
||||||
|
gem 'pry-rails', '~> 0.3'
|
||||||
gem 'rspec-rails', '~> 6.0'
|
gem 'rspec-rails', '~> 6.0'
|
||||||
gem 'rubocop-performance', require: false
|
gem 'rubocop-performance', require: false
|
||||||
gem 'rubocop-rails', require: false
|
gem 'rubocop-rails', require: false
|
||||||
|
@ -117,6 +119,7 @@ end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
gem 'capybara', '~> 3.38'
|
gem 'capybara', '~> 3.38'
|
||||||
|
gem 'climate_control', '~> 0.2'
|
||||||
gem 'faker', '~> 3.1'
|
gem 'faker', '~> 3.1'
|
||||||
gem 'json-schema', '~> 3.0'
|
gem 'json-schema', '~> 3.0'
|
||||||
gem 'rack-test', '~> 2.0'
|
gem 'rack-test', '~> 2.0'
|
||||||
|
|
12
Gemfile.lock
12
Gemfile.lock
|
@ -155,6 +155,7 @@ GEM
|
||||||
bundler-audit (0.9.1)
|
bundler-audit (0.9.1)
|
||||||
bundler (>= 1.2.0, < 3)
|
bundler (>= 1.2.0, < 3)
|
||||||
thor (~> 1.0)
|
thor (~> 1.0)
|
||||||
|
byebug (11.1.3)
|
||||||
capistrano (3.17.2)
|
capistrano (3.17.2)
|
||||||
airbrussh (>= 1.0.0)
|
airbrussh (>= 1.0.0)
|
||||||
i18n
|
i18n
|
||||||
|
@ -496,6 +497,14 @@ GEM
|
||||||
net-smtp
|
net-smtp
|
||||||
premailer (~> 1.7, >= 1.7.9)
|
premailer (~> 1.7, >= 1.7.9)
|
||||||
private_address_check (0.5.0)
|
private_address_check (0.5.0)
|
||||||
|
pry (0.14.1)
|
||||||
|
coderay (~> 1.1)
|
||||||
|
method_source (~> 1.0)
|
||||||
|
pry-byebug (3.10.1)
|
||||||
|
byebug (~> 11.0)
|
||||||
|
pry (>= 0.13, < 0.15)
|
||||||
|
pry-rails (0.3.9)
|
||||||
|
pry (>= 0.10.4)
|
||||||
public_suffix (5.0.1)
|
public_suffix (5.0.1)
|
||||||
puma (6.1.0)
|
puma (6.1.0)
|
||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
|
@ -783,6 +792,7 @@ DEPENDENCIES
|
||||||
capybara (~> 3.38)
|
capybara (~> 3.38)
|
||||||
charlock_holmes (~> 0.7.7)
|
charlock_holmes (~> 0.7.7)
|
||||||
chewy (~> 7.2)
|
chewy (~> 7.2)
|
||||||
|
climate_control (~> 0.2)
|
||||||
cocoon (~> 1.2)
|
cocoon (~> 1.2)
|
||||||
color_diff (~> 0.1)
|
color_diff (~> 0.1)
|
||||||
concurrent-ruby
|
concurrent-ruby
|
||||||
|
@ -840,6 +850,8 @@ DEPENDENCIES
|
||||||
posix-spawn
|
posix-spawn
|
||||||
premailer-rails
|
premailer-rails
|
||||||
private_address_check (~> 0.5)
|
private_address_check (~> 0.5)
|
||||||
|
pry-byebug (~> 3.10)
|
||||||
|
pry-rails (~> 0.3)
|
||||||
public_suffix (~> 5.0)
|
public_suffix (~> 5.0)
|
||||||
puma (~> 6.1)
|
puma (~> 6.1)
|
||||||
pundit (~> 2.3)
|
pundit (~> 2.3)
|
||||||
|
|
|
@ -19,8 +19,6 @@ class RelationshipsController < ApplicationController
|
||||||
@form.save
|
@form.save
|
||||||
rescue ActionController::ParameterMissing
|
rescue ActionController::ParameterMissing
|
||||||
# Do nothing
|
# Do nothing
|
||||||
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound
|
|
||||||
flash[:alert] = I18n.t('relationships.follow_failure') if action_from_button == 'follow'
|
|
||||||
ensure
|
ensure
|
||||||
redirect_to relationships_path(filter_params)
|
redirect_to relationships_path(filter_params)
|
||||||
end
|
end
|
||||||
|
@ -62,8 +60,8 @@ class RelationshipsController < ApplicationController
|
||||||
'unfollow'
|
'unfollow'
|
||||||
elsif params[:remove_from_followers]
|
elsif params[:remove_from_followers]
|
||||||
'remove_from_followers'
|
'remove_from_followers'
|
||||||
elsif params[:block_domains] || params[:remove_domains_from_followers]
|
elsif params[:block_domains]
|
||||||
'remove_domains_from_followers'
|
'block_domains'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import PollContainer from 'mastodon/containers/poll_container';
|
import PollContainer from 'mastodon/containers/poll_container';
|
||||||
import Icon from 'mastodon/components/icon';
|
import Icon from 'mastodon/components/icon';
|
||||||
import { autoPlayGif, languages as preloadedLanguages } from 'mastodon/initial_state';
|
import { autoPlayGif, languages as preloadedLanguages, translationEnabled } from 'mastodon/initial_state';
|
||||||
|
|
||||||
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
const MAX_HEIGHT = 706; // 22px * 32 (+ 2px padding at the top)
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ class StatusContent extends React.PureComponent {
|
||||||
|
|
||||||
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
const hidden = this.props.onExpandedToggle ? !this.props.expanded : this.state.hidden;
|
||||||
const renderReadMore = this.props.onClick && status.get('collapsed');
|
const renderReadMore = this.props.onClick && status.get('collapsed');
|
||||||
const renderTranslate = this.props.onTranslate && status.get('translatable');
|
const renderTranslate = translationEnabled && this.context.identity.signedIn && this.props.onTranslate && ['public', 'unlisted'].includes(status.get('visibility')) && status.get('contentHtml').length > 0 && status.get('language') !== null && intl.locale !== status.get('language');
|
||||||
|
|
||||||
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
const content = { __html: status.get('translation') ? status.getIn(['translation', 'content']) : status.get('contentHtml') };
|
||||||
const spoilerContent = { __html: status.get('spoilerHtml') };
|
const spoilerContent = { __html: status.get('spoilerHtml') };
|
||||||
|
|
|
@ -22,8 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @withRouter
|
export default @connect(null, mapDispatchToProps)
|
||||||
@connect(null, mapDispatchToProps)
|
@withRouter
|
||||||
class Header extends React.PureComponent {
|
class Header extends React.PureComponent {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
|
|
|
@ -82,8 +82,8 @@ class NavigationPanel extends React.Component {
|
||||||
{signedIn && (
|
{signedIn && (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
<ColumnLink transparent to='/conversations' icon='at' text={intl.formatMessage(messages.direct)} />
|
||||||
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
|
||||||
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
<ColumnLink transparent to='/favourites' icon='star' text={intl.formatMessage(messages.favourites)} />
|
||||||
|
<ColumnLink transparent to='/bookmarks' icon='bookmark' text={intl.formatMessage(messages.bookmarks)} />
|
||||||
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
<ColumnLink transparent to='/lists' icon='list-ul' text={intl.formatMessage(messages.lists)} />
|
||||||
|
|
||||||
<ListPanel />
|
<ListPanel />
|
||||||
|
|
|
@ -80,6 +80,7 @@
|
||||||
* @property {boolean} use_blurhash
|
* @property {boolean} use_blurhash
|
||||||
* @property {boolean=} use_pending_items
|
* @property {boolean=} use_pending_items
|
||||||
* @property {string} version
|
* @property {string} version
|
||||||
|
* @property {boolean} translation_enabled
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,6 +132,7 @@ export const unfollowModal = getMeta('unfollow_modal');
|
||||||
export const useBlurhash = getMeta('use_blurhash');
|
export const useBlurhash = getMeta('use_blurhash');
|
||||||
export const usePendingItems = getMeta('use_pending_items');
|
export const usePendingItems = getMeta('use_pending_items');
|
||||||
export const version = getMeta('version');
|
export const version = getMeta('version');
|
||||||
|
export const translationEnabled = getMeta('translation_enabled');
|
||||||
export const languages = initialState?.languages;
|
export const languages = initialState?.languages;
|
||||||
export const statusPageUrl = getMeta('status_page_url');
|
export const statusPageUrl = getMeta('status_page_url');
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,3 @@
|
||||||
@import 'mastodon/dashboard';
|
@import 'mastodon/dashboard';
|
||||||
@import 'mastodon/rtl';
|
@import 'mastodon/rtl';
|
||||||
@import 'mastodon/accessibility';
|
@import 'mastodon/accessibility';
|
||||||
@import 'mastodon/rich_text';
|
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
.status__content__text,
|
|
||||||
.e-content,
|
|
||||||
.reply-indicator__content {
|
|
||||||
pre,
|
|
||||||
blockquote {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
unicode-bidi: plaintext;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
padding-left: 10px;
|
|
||||||
border-left: 3px solid $darker-text-color;
|
|
||||||
color: $darker-text-color;
|
|
||||||
white-space: normal;
|
|
||||||
|
|
||||||
p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
& > ul,
|
|
||||||
& > ol {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
em,
|
|
||||||
i {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul,
|
|
||||||
ol {
|
|
||||||
margin-left: 2em;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: disc;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol {
|
|
||||||
list-style-type: decimal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-indicator__content {
|
|
||||||
blockquote {
|
|
||||||
border-left-color: $inverted-text-color;
|
|
||||||
color: $inverted-text-color;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,10 +21,6 @@ class TranslationService
|
||||||
ENV['DEEPL_API_KEY'].present? || ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
|
ENV['DEEPL_API_KEY'].present? || ENV['LIBRE_TRANSLATE_ENDPOINT'].present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def supported?(_source_language, _target_language)
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def translate(_text, _source_language, _target_language)
|
def translate(_text, _source_language, _target_language)
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,53 +11,33 @@ class TranslationService::DeepL < TranslationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate(text, source_language, target_language)
|
def translate(text, source_language, target_language)
|
||||||
form = { text: text, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' }
|
request(text, source_language, target_language).perform do |res|
|
||||||
request(:post, '/v2/translate', form: form) do |res|
|
|
||||||
transform_response(res.body_with_limit)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def supported?(source_language, target_language)
|
|
||||||
source_language.in?(languages('source')) && target_language.in?(languages('target'))
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def languages(type)
|
|
||||||
Rails.cache.fetch("translation_service/deepl/languages/#{type}", expires_in: 7.days, race_condition_ttl: 1.minute) do
|
|
||||||
request(:get, "/v2/languages?type=#{type}") do |res|
|
|
||||||
# In DeepL, EN and PT are deprecated in favor of EN-GB/EN-US and PT-BR/PT-PT, so
|
|
||||||
# they are supported but not returned by the API.
|
|
||||||
extra = type == 'source' ? [nil] : %w(en pt)
|
|
||||||
languages = Oj.load(res.body_with_limit).map { |language| language['language'].downcase }
|
|
||||||
|
|
||||||
languages + extra
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def request(verb, path, **options)
|
|
||||||
req = Request.new(verb, "#{base_url}#{path}", **options)
|
|
||||||
req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
|
|
||||||
req.perform do |res|
|
|
||||||
case res.code
|
case res.code
|
||||||
when 429
|
when 429
|
||||||
raise TooManyRequestsError
|
raise TooManyRequestsError
|
||||||
when 456
|
when 456
|
||||||
raise QuotaExceededError
|
raise QuotaExceededError
|
||||||
when 200...300
|
when 200...300
|
||||||
yield res
|
transform_response(res.body_with_limit)
|
||||||
else
|
else
|
||||||
raise UnexpectedResponseError
|
raise UnexpectedResponseError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def base_url
|
private
|
||||||
|
|
||||||
|
def request(text, source_language, target_language)
|
||||||
|
req = Request.new(:post, endpoint_url, form: { text: text, source_lang: source_language&.upcase, target_lang: target_language, tag_handling: 'html' })
|
||||||
|
req.add_headers(Authorization: "DeepL-Auth-Key #{@api_key}")
|
||||||
|
req
|
||||||
|
end
|
||||||
|
|
||||||
|
def endpoint_url
|
||||||
if @plan == 'free'
|
if @plan == 'free'
|
||||||
'https://api-free.deepl.com'
|
'https://api-free.deepl.com/v2/translate'
|
||||||
else
|
else
|
||||||
'https://api.deepl.com'
|
'https://api.deepl.com/v2/translate'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,45 +9,29 @@ class TranslationService::LibreTranslate < TranslationService
|
||||||
end
|
end
|
||||||
|
|
||||||
def translate(text, source_language, target_language)
|
def translate(text, source_language, target_language)
|
||||||
body = Oj.dump(q: text, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key)
|
request(text, source_language, target_language).perform do |res|
|
||||||
request(:post, '/translate', body: body) do |res|
|
|
||||||
transform_response(res.body_with_limit, source_language)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def supported?(source_language, target_language)
|
|
||||||
languages.key?(source_language) && languages[source_language].include?(target_language)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def languages
|
|
||||||
Rails.cache.fetch('translation_service/libre_translate/languages', expires_in: 7.days, race_condition_ttl: 1.minute) do
|
|
||||||
request(:get, '/languages') do |res|
|
|
||||||
languages = Oj.load(res.body_with_limit).to_h { |language| [language['code'], language['targets']] }
|
|
||||||
languages[nil] = languages.values.flatten.uniq
|
|
||||||
languages
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def request(verb, path, **options)
|
|
||||||
req = Request.new(verb, "#{@base_url}#{path}", allow_local: true, **options)
|
|
||||||
req.add_headers('Content-Type': 'application/json')
|
|
||||||
req.perform do |res|
|
|
||||||
case res.code
|
case res.code
|
||||||
when 429
|
when 429
|
||||||
raise TooManyRequestsError
|
raise TooManyRequestsError
|
||||||
when 403
|
when 403
|
||||||
raise QuotaExceededError
|
raise QuotaExceededError
|
||||||
when 200...300
|
when 200...300
|
||||||
yield res
|
transform_response(res.body_with_limit, source_language)
|
||||||
else
|
else
|
||||||
raise UnexpectedResponseError
|
raise UnexpectedResponseError
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def request(text, source_language, target_language)
|
||||||
|
body = Oj.dump(q: text, source: source_language.presence || 'auto', target: target_language, format: 'html', api_key: @api_key)
|
||||||
|
req = Request.new(:post, "#{@base_url}/translate", body: body, allow_local: true)
|
||||||
|
req.add_headers('Content-Type': 'application/json')
|
||||||
|
req
|
||||||
|
end
|
||||||
|
|
||||||
def transform_response(str, source_language)
|
def transform_response(str, source_language)
|
||||||
json = Oj.load(str, mode: :strict)
|
json = Oj.load(str, mode: :strict)
|
||||||
|
|
||||||
|
|
|
@ -7,17 +7,9 @@ class ApplicationMailer < ActionMailer::Base
|
||||||
helper :instance
|
helper :instance
|
||||||
helper :formatting
|
helper :formatting
|
||||||
|
|
||||||
after_action :set_autoreply_headers!
|
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def locale_for_account(account, &block)
|
def locale_for_account(account, &block)
|
||||||
I18n.with_locale(account.user_locale || I18n.default_locale, &block)
|
I18n.with_locale(account.user_locale || I18n.default_locale, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_autoreply_headers!
|
|
||||||
headers['Precedence'] = 'list'
|
|
||||||
headers['X-Auto-Response-Suppress'] = 'All'
|
|
||||||
headers['Auto-Submitted'] = 'auto-generated'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -61,7 +61,7 @@ module Omniauthable
|
||||||
user.account.avatar_remote_url = nil
|
user.account.avatar_remote_url = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
user.confirm! if email_is_verified
|
user.skip_confirmation! if email_is_verified
|
||||||
user.save!
|
user.save!
|
||||||
user
|
user
|
||||||
end
|
end
|
||||||
|
|
|
@ -17,8 +17,8 @@ class Form::AccountBatch
|
||||||
unfollow!
|
unfollow!
|
||||||
when 'remove_from_followers'
|
when 'remove_from_followers'
|
||||||
remove_from_followers!
|
remove_from_followers!
|
||||||
when 'remove_domains_from_followers'
|
when 'block_domains'
|
||||||
remove_domains_from_followers!
|
block_domains!
|
||||||
when 'approve'
|
when 'approve'
|
||||||
approve!
|
approve!
|
||||||
when 'reject'
|
when 'reject'
|
||||||
|
@ -35,15 +35,9 @@ class Form::AccountBatch
|
||||||
private
|
private
|
||||||
|
|
||||||
def follow!
|
def follow!
|
||||||
error = nil
|
|
||||||
|
|
||||||
accounts.each do |target_account|
|
accounts.each do |target_account|
|
||||||
FollowService.new.call(current_account, target_account)
|
FollowService.new.call(current_account, target_account)
|
||||||
rescue Mastodon::NotPermittedError, ActiveRecord::RecordNotFound => e
|
|
||||||
error ||= e
|
|
||||||
end
|
end
|
||||||
|
|
||||||
raise error if error.present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unfollow!
|
def unfollow!
|
||||||
|
@ -56,8 +50,10 @@ class Form::AccountBatch
|
||||||
RemoveFromFollowersService.new.call(current_account, account_ids)
|
RemoveFromFollowersService.new.call(current_account, account_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_domains_from_followers!
|
def block_domains!
|
||||||
RemoveDomainsFromFollowersService.new.call(current_account, account_domains)
|
AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain|
|
||||||
|
[current_account.id, domain]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def account_domains
|
def account_domains
|
||||||
|
|
|
@ -232,16 +232,6 @@ class Status < ApplicationRecord
|
||||||
public_visibility? || unlisted_visibility?
|
public_visibility? || unlisted_visibility?
|
||||||
end
|
end
|
||||||
|
|
||||||
def translatable?
|
|
||||||
translate_target_locale = I18n.locale.to_s.split(/[_-]/).first
|
|
||||||
|
|
||||||
distributable? &&
|
|
||||||
content.present? &&
|
|
||||||
language != translate_target_locale &&
|
|
||||||
TranslationService.configured? &&
|
|
||||||
TranslationService.configured.supported?(language, translate_target_locale)
|
|
||||||
end
|
|
||||||
|
|
||||||
alias sign? distributable?
|
alias sign? distributable?
|
||||||
|
|
||||||
def with_media?
|
def with_media?
|
||||||
|
|
|
@ -30,6 +30,7 @@ class InitialStateSerializer < ActiveModel::Serializer
|
||||||
timeline_preview: Setting.timeline_preview,
|
timeline_preview: Setting.timeline_preview,
|
||||||
activity_api_enabled: Setting.activity_api_enabled,
|
activity_api_enabled: Setting.activity_api_enabled,
|
||||||
single_user_mode: Rails.configuration.x.single_user_mode,
|
single_user_mode: Rails.configuration.x.single_user_mode,
|
||||||
|
translation_enabled: TranslationService.configured?,
|
||||||
trends_as_landing_page: Setting.trends_as_landing_page,
|
trends_as_landing_page: Setting.trends_as_landing_page,
|
||||||
status_page_url: Setting.status_page_url,
|
status_page_url: Setting.status_page_url,
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||||
:sensitive, :spoiler_text, :visibility, :language, :translatable,
|
:sensitive, :spoiler_text, :visibility, :language,
|
||||||
:uri, :url, :replies_count, :reblogs_count,
|
:uri, :url, :replies_count, :reblogs_count,
|
||||||
:favourites_count, :edited_at
|
:favourites_count, :edited_at
|
||||||
|
|
||||||
|
@ -50,10 +50,6 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
|
object.account.user_shows_application? || (current_user? && current_user.account_id == object.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def translatable
|
|
||||||
current_user? && object.translatable?
|
|
||||||
end
|
|
||||||
|
|
||||||
def visibility
|
def visibility
|
||||||
# This visibility is masked behind "private"
|
# This visibility is masked behind "private"
|
||||||
# to avoid API changes because there are no
|
# to avoid API changes because there are no
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class FollowMigrationService < FollowService
|
|
||||||
# Follow an account with the same settings as another account, and unfollow the old account once the request is sent
|
|
||||||
# @param [Account] source_account From which to follow
|
|
||||||
# @param [Account] target_account Account to follow
|
|
||||||
# @param [Account] old_target_account Account to unfollow once the follow request has been sent to the new one
|
|
||||||
# @option [Boolean] bypass_locked Whether to immediately follow the new account even if it is locked
|
|
||||||
def call(source_account, target_account, old_target_account, bypass_locked: false)
|
|
||||||
@old_target_account = old_target_account
|
|
||||||
|
|
||||||
follow = source_account.active_relationships.find_by(target_account: old_target_account)
|
|
||||||
reblogs = follow&.show_reblogs?
|
|
||||||
notify = follow&.notify?
|
|
||||||
languages = follow&.languages
|
|
||||||
|
|
||||||
super(source_account, target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_locked: bypass_locked, bypass_limit: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def request_follow!
|
|
||||||
follow_request = @source_account.request_follow!(@target_account, **follow_options.merge(rate_limit: @options[:with_rate_limit], bypass_limit: @options[:bypass_limit]))
|
|
||||||
|
|
||||||
if @target_account.local?
|
|
||||||
LocalNotificationWorker.perform_async(@target_account.id, follow_request.id, follow_request.class.name, 'follow_request')
|
|
||||||
UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true)
|
|
||||||
elsif @target_account.activitypub?
|
|
||||||
ActivityPub::MigratedFollowDeliveryWorker.perform_async(build_json(follow_request), @source_account.id, @target_account.inbox_url, @old_target_account.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
follow_request
|
|
||||||
end
|
|
||||||
|
|
||||||
def direct_follow!
|
|
||||||
follow = super
|
|
||||||
UnfollowService.new.call(@source_account, @old_target_account, skip_unmerge: true)
|
|
||||||
follow
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,23 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class RemoveDomainsFromFollowersService < BaseService
|
|
||||||
include Payloadable
|
|
||||||
|
|
||||||
def call(source_account, target_domains)
|
|
||||||
source_account.passive_relationships.where(account_id: Account.where(domain: target_domains)).find_each do |follow|
|
|
||||||
follow.destroy
|
|
||||||
|
|
||||||
create_notification(follow) if source_account.local? && !follow.account.local? && follow.account.activitypub?
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def create_notification(follow)
|
|
||||||
ActivityPub::DeliveryWorker.perform_async(build_json(follow), follow.target_account_id, follow.account.inbox_url)
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_json(follow)
|
|
||||||
Oj.dump(serialize_payload(follow, ActivityPub::RejectFollowSerializer))
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -6,7 +6,7 @@ class TranslateStatusService < BaseService
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
def call(status, target_language)
|
def call(status, target_language)
|
||||||
raise Mastodon::NotPermittedError unless status.translatable?
|
raise Mastodon::NotPermittedError unless status.public_visibility? || status.unlisted_visibility?
|
||||||
|
|
||||||
@status = status
|
@status = status
|
||||||
@content = status_content_format(@status)
|
@content = status_content_format(@status)
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
%td
|
%td
|
||||||
- if @status.trend.allowed?
|
- if @status.trend.allowed?
|
||||||
%abbr{ title: t('admin.trends.tags.current_score', score: @status.trend.score) }= t('admin.trends.tags.trending_rank', rank: @status.trend.rank)
|
%abbr{ title: t('admin.trends.tags.current_score', score: @status.trend.score) }= t('admin.trends.tags.trending_rank', rank: @status.trend.rank)
|
||||||
- elsif @status.requires_review?
|
- elsif @status.trend.requires_review?
|
||||||
= t('admin.trends.pending_review')
|
= t('admin.trends.pending_review')
|
||||||
- else
|
- else
|
||||||
= t('admin.trends.not_allowed_to_trend')
|
= t('admin.trends.not_allowed_to_trend')
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
|
|
||||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_followers') } unless following_relationship?
|
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_followers')]), name: :remove_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('relationships.confirm_remove_selected_followers') } unless following_relationship?
|
||||||
|
|
||||||
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :remove_domains_from_followers, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
|
= f.button safe_join([fa_icon('trash'), t('relationships.remove_selected_domains')]), name: :block_domains, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') } if followed_by_relationship?
|
||||||
.batch-table__body
|
.batch-table__body
|
||||||
- if @accounts.empty?
|
- if @accounts.empty?
|
||||||
= nothing_here 'nothing-here--under-tabs'
|
= nothing_here 'nothing-here--under-tabs'
|
||||||
|
|
|
@ -10,16 +10,6 @@ class ActivityPub::DeliveryWorker
|
||||||
|
|
||||||
sidekiq_options queue: 'push', retry: 16, dead: false
|
sidekiq_options queue: 'push', retry: 16, dead: false
|
||||||
|
|
||||||
# Unfortunately, we cannot control Sidekiq's jitter, so add our own
|
|
||||||
sidekiq_retry_in do |count|
|
|
||||||
# This is Sidekiq's default delay
|
|
||||||
delay = (count**4) + 15
|
|
||||||
# Our custom jitter, that will be added to Sidekiq's built-in one.
|
|
||||||
# Sidekiq's built-in jitter is `rand(10) * (count + 1)`
|
|
||||||
jitter = rand(0.5 * (count**4))
|
|
||||||
delay + jitter
|
|
||||||
end
|
|
||||||
|
|
||||||
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
HEADERS = { 'Content-Type' => 'application/activity+json' }.freeze
|
||||||
|
|
||||||
def perform(json, source_account_id, inbox_url, options = {})
|
def perform(json, source_account_id, inbox_url, options = {})
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class ActivityPub::MigratedFollowDeliveryWorker < ActivityPub::DeliveryWorker
|
|
||||||
def perform(json, source_account_id, inbox_url, old_target_account_id, options = {})
|
|
||||||
super(json, source_account_id, inbox_url, options)
|
|
||||||
unfollow_old_account!(old_target_account_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def unfollow_old_account!(old_target_account_id)
|
|
||||||
old_target_account = Account.find(old_target_account_id)
|
|
||||||
UnfollowService.new.call(@source_account, old_target_account, skip_unmerge: true)
|
|
||||||
rescue
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -10,7 +10,13 @@ class UnfollowFollowWorker
|
||||||
old_target_account = Account.find(old_target_account_id)
|
old_target_account = Account.find(old_target_account_id)
|
||||||
new_target_account = Account.find(new_target_account_id)
|
new_target_account = Account.find(new_target_account_id)
|
||||||
|
|
||||||
FollowMigrationService.new.call(follower_account, new_target_account, old_target_account, bypass_locked: bypass_locked)
|
follow = follower_account.active_relationships.find_by(target_account: old_target_account)
|
||||||
|
reblogs = follow&.show_reblogs?
|
||||||
|
notify = follow&.notify?
|
||||||
|
languages = follow&.languages
|
||||||
|
|
||||||
|
FollowService.new.call(follower_account, new_target_account, reblogs: reblogs, notify: notify, languages: languages, bypass_locked: bypass_locked, bypass_limit: true)
|
||||||
|
UnfollowService.new.call(follower_account, old_target_account, skip_unmerge: true)
|
||||||
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
rescue ActiveRecord::RecordNotFound, Mastodon::NotPermittedError
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
|
@ -128,7 +128,6 @@ Rails.application.configure do
|
||||||
enable_starttls_auto: enable_starttls_auto,
|
enable_starttls_auto: enable_starttls_auto,
|
||||||
tls: ENV['SMTP_TLS'].presence && ENV['SMTP_TLS'] == 'true',
|
tls: ENV['SMTP_TLS'].presence && ENV['SMTP_TLS'] == 'true',
|
||||||
ssl: ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true',
|
ssl: ENV['SMTP_SSL'].presence && ENV['SMTP_SSL'] == 'true',
|
||||||
read_timeout: 20,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
config.action_mailer.delivery_method = ENV.fetch('SMTP_DELIVERY_METHOD', 'smtp').to_sym
|
||||||
|
|
|
@ -90,12 +90,6 @@ if ENV['S3_ENABLED'] == 'true'
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if ENV.has_key?('S3_STORAGE_CLASS')
|
|
||||||
Paperclip::Attachment.default_options[:s3_headers].merge!(
|
|
||||||
'X-Amz-Storage-Class' => ENV['S3_STORAGE_CLASS']
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
# Some S3-compatible providers might not actually be compatible with some APIs
|
# Some S3-compatible providers might not actually be compatible with some APIs
|
||||||
# used by kt-paperclip, see https://github.com/mastodon/mastodon/issues/16822
|
# used by kt-paperclip, see https://github.com/mastodon/mastodon/issues/16822
|
||||||
if ENV['S3_FORCE_SINGLE_REQUEST'] == 'true'
|
if ENV['S3_FORCE_SINGLE_REQUEST'] == 'true'
|
||||||
|
|
|
@ -1408,7 +1408,6 @@ en:
|
||||||
confirm_remove_selected_followers: Are you sure you want to remove selected followers?
|
confirm_remove_selected_followers: Are you sure you want to remove selected followers?
|
||||||
confirm_remove_selected_follows: Are you sure you want to remove selected follows?
|
confirm_remove_selected_follows: Are you sure you want to remove selected follows?
|
||||||
dormant: Dormant
|
dormant: Dormant
|
||||||
follow_failure: Could not follow some of the selected accounts.
|
|
||||||
follow_selected_followers: Follow selected followers
|
follow_selected_followers: Follow selected followers
|
||||||
followers: Followers
|
followers: Followers
|
||||||
following: Following
|
following: Following
|
||||||
|
|
|
@ -627,7 +627,7 @@ module Mastodon
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless options[:force] || migration.target_account_id == account.moved_to_account_id
|
unless options[:force] || migration.target_acount_id == account.moved_to_account_id
|
||||||
say('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway', :red)
|
say('The specified account is not redirecting to its last migration target. Use --force if you want to replay the migration anyway', :red)
|
||||||
exit(1)
|
exit(1)
|
||||||
end
|
end
|
||||||
|
|
|
@ -51,22 +51,29 @@ class Sanitize
|
||||||
end
|
end
|
||||||
|
|
||||||
UNSUPPORTED_ELEMENTS_TRANSFORMER = lambda do |env|
|
UNSUPPORTED_ELEMENTS_TRANSFORMER = lambda do |env|
|
||||||
return unless %w(h1 h2 h3 h4 h5 h6).include?(env[:node_name])
|
return unless %w(h1 h2 h3 h4 h5 h6 blockquote pre ul ol li).include?(env[:node_name])
|
||||||
|
|
||||||
current_node = env[:node]
|
current_node = env[:node]
|
||||||
|
|
||||||
current_node.name = 'strong'
|
case env[:node_name]
|
||||||
current_node.wrap('<p></p>')
|
when 'li'
|
||||||
|
current_node.traverse do |node|
|
||||||
|
next unless %w(p ul ol li).include?(node.name)
|
||||||
|
|
||||||
|
node.add_next_sibling('<br>') if node.next_sibling
|
||||||
|
node.replace(node.children) unless node.text?
|
||||||
|
end
|
||||||
|
else
|
||||||
|
current_node.name = 'p'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
MASTODON_STRICT ||= freeze_config(
|
MASTODON_STRICT ||= freeze_config(
|
||||||
elements: %w(p br span a del pre blockquote code b strong u i em ul ol li),
|
elements: %w(p br span a),
|
||||||
|
|
||||||
attributes: {
|
attributes: {
|
||||||
'a' => %w(href rel class),
|
'a' => %w(href rel class),
|
||||||
'span' => %w(class),
|
'span' => %w(class),
|
||||||
'ol' => %w(start reversed),
|
|
||||||
'li' => %w(value),
|
|
||||||
},
|
},
|
||||||
|
|
||||||
add_attributes: {
|
add_attributes: {
|
||||||
|
|
|
@ -83,7 +83,6 @@
|
||||||
"object.values": "^1.1.6",
|
"object.values": "^1.1.6",
|
||||||
"path-complete-extname": "^1.0.0",
|
"path-complete-extname": "^1.0.0",
|
||||||
"pg": "^8.5.0",
|
"pg": "^8.5.0",
|
||||||
"pg-connection-string": "^2.5.0",
|
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
"promise.prototype.finally": "^3.1.4",
|
"promise.prototype.finally": "^3.1.4",
|
||||||
|
|
|
@ -57,9 +57,6 @@ describe Admin::Reports::ActionsController do
|
||||||
let!(:media) { Fabricate(:media_attachment, account: target_account, status: statuses[0]) }
|
let!(:media) { Fabricate(:media_attachment, account: target_account, status: statuses[0]) }
|
||||||
let(:report) { Fabricate(:report, target_account: target_account, status_ids: statuses.map(&:id)) }
|
let(:report) { Fabricate(:report, target_account: target_account, status_ids: statuses.map(&:id)) }
|
||||||
let(:text) { 'hello' }
|
let(:text) { 'hello' }
|
||||||
let(:common_params) do
|
|
||||||
{ report_id: report.id, text: text }
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples 'common behavior' do
|
shared_examples 'common behavior' do
|
||||||
it 'closes the report' do
|
it 'closes the report' do
|
||||||
|
@ -75,26 +72,6 @@ describe Admin::Reports::ActionsController do
|
||||||
subject
|
subject
|
||||||
expect(response).to redirect_to(admin_reports_path)
|
expect(response).to redirect_to(admin_reports_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when text is unset' do
|
|
||||||
let(:common_params) do
|
|
||||||
{ report_id: report.id }
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'closes the report' do
|
|
||||||
expect { subject }.to change { report.reload.action_taken? }.from(false).to(true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'creates a strike with the expected text' do
|
|
||||||
expect { subject }.to change { report.target_account.strikes.count }.by(1)
|
|
||||||
expect(report.target_account.strikes.last.text).to eq ''
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'redirects' do
|
|
||||||
subject
|
|
||||||
expect(response).to redirect_to(admin_reports_path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
shared_examples 'all action types' do
|
shared_examples 'all action types' do
|
||||||
|
@ -147,13 +124,13 @@ describe Admin::Reports::ActionsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'action as submit button' do
|
context 'action as submit button' do
|
||||||
subject { post :create, params: common_params.merge({ action => '' }) }
|
subject { post :create, params: { report_id: report.id, text: text, action => '' } }
|
||||||
|
|
||||||
it_behaves_like 'all action types'
|
it_behaves_like 'all action types'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'action as submit button' do
|
context 'action as submit button' do
|
||||||
subject { post :create, params: common_params.merge({ moderation_action: action }) }
|
subject { post :create, params: { report_id: report.id, text: text, moderation_action: action } }
|
||||||
|
|
||||||
it_behaves_like 'all action types'
|
it_behaves_like 'all action types'
|
||||||
end
|
end
|
||||||
|
|
|
@ -58,7 +58,7 @@ describe RelationshipsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when select parameter is provided' do
|
context 'when select parameter is provided' do
|
||||||
subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, remove_domains_from_followers: '' } }
|
subject { patch :update, params: { form_account_batch: { account_ids: [poopfeast.id] }, block_domains: '' } }
|
||||||
|
|
||||||
it 'soft-blocks followers from selected domains' do
|
it 'soft-blocks followers from selected domains' do
|
||||||
poopfeast.follow!(user.account)
|
poopfeast.follow!(user.account)
|
||||||
|
@ -69,15 +69,6 @@ describe RelationshipsController do
|
||||||
expect(poopfeast.following?(user.account)).to be false
|
expect(poopfeast.following?(user.account)).to be false
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not unfollow users from selected domains' do
|
|
||||||
user.account.follow!(poopfeast)
|
|
||||||
|
|
||||||
sign_in user, scope: :user
|
|
||||||
subject
|
|
||||||
|
|
||||||
expect(user.account.following?(poopfeast)).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
include_examples 'authenticate user'
|
include_examples 'authenticate user'
|
||||||
include_examples 'redirects back to followers page'
|
include_examples 'redirects back to followers page'
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,16 +6,24 @@ describe Sanitize::Config do
|
||||||
describe '::MASTODON_STRICT' do
|
describe '::MASTODON_STRICT' do
|
||||||
subject { Sanitize::Config::MASTODON_STRICT }
|
subject { Sanitize::Config::MASTODON_STRICT }
|
||||||
|
|
||||||
it 'converts h1 to p strong' do
|
it 'converts h1 to p' do
|
||||||
expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<p><strong>Foo</strong></p>'
|
expect(Sanitize.fragment('<h1>Foo</h1>', subject)).to eq '<p>Foo</p>'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps ul' do
|
it 'converts ul to p' do
|
||||||
expect(Sanitize.fragment('<p>Check out:</p><ul><li>Foo</li><li>Bar</li></ul>', subject)).to eq '<p>Check out:</p><ul><li>Foo</li><li>Bar</li></ul>'
|
expect(Sanitize.fragment('<p>Check out:</p><ul><li>Foo</li><li>Bar</li></ul>', subject)).to eq '<p>Check out:</p><p>Foo<br>Bar</p>'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'keeps start and reversed attributes of ol' do
|
it 'converts p inside ul' do
|
||||||
expect(Sanitize.fragment('<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>', subject)).to eq '<p>Check out:</p><ol start="3" reversed=""><li>Foo</li><li>Bar</li></ol>'
|
expect(Sanitize.fragment('<ul><li><p>Foo</p><p>Bar</p></li><li>Baz</li></ul>', subject)).to eq '<p>Foo<br>Bar<br>Baz</p>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'converts ul inside ul' do
|
||||||
|
expect(Sanitize.fragment('<ul><li>Foo</li><li><ul><li>Bar</li><li>Baz</li></ul></li></ul>', subject)).to eq '<p>Foo<br>Bar<br>Baz</p>'
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'keep links in lists' do
|
||||||
|
expect(Sanitize.fragment('<p>Check out:</p><ul><li><a href="https://joinmastodon.org" rel="nofollow noopener noreferrer" target="_blank">joinmastodon.org</a></li><li>Bar</li></ul>', subject)).to eq '<p>Check out:</p><p><a href="https://joinmastodon.org" rel="nofollow noopener noreferrer" target="_blank">joinmastodon.org</a><br>Bar</p>'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes a without href' do
|
it 'removes a without href' do
|
||||||
|
@ -37,13 +45,5 @@ describe Sanitize::Config do
|
||||||
it 'keeps a with href' do
|
it 'keeps a with href' do
|
||||||
expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
|
expect(Sanitize.fragment('<a href="http://example.com">Test</a>', subject)).to eq '<a href="http://example.com" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'removes a with unparsable href' do
|
|
||||||
expect(Sanitize.fragment('<a href=" https://google.fr">Test</a>', subject)).to eq 'Test'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'keeps a with supported scheme and no host' do
|
|
||||||
expect(Sanitize.fragment('<a href="dweb:/a/foo">Test</a>', subject)).to eq '<a href="dweb:/a/foo" rel="nofollow noopener noreferrer" target="_blank">Test</a>'
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe TranslationService::DeepL do
|
|
||||||
subject(:service) { described_class.new(plan, 'my-api-key') }
|
|
||||||
|
|
||||||
let(:plan) { 'advanced' }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_request(:get, 'https://api.deepl.com/v2/languages?type=source').to_return(
|
|
||||||
body: '[{"language":"EN","name":"English"},{"language":"UK","name":"Ukrainian"}]'
|
|
||||||
)
|
|
||||||
stub_request(:get, 'https://api.deepl.com/v2/languages?type=target').to_return(
|
|
||||||
body: '[{"language":"EN-GB","name":"English (British)"},{"language":"ZH","name":"Chinese"}]'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#supported?' do
|
|
||||||
it 'supports included languages as source and target languages' do
|
|
||||||
expect(service.supported?('uk', 'en')).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'supports auto-detecting source language' do
|
|
||||||
expect(service.supported?(nil, 'en')).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'supports "en" and "pt" as target languages though not included in language list' do
|
|
||||||
expect(service.supported?('uk', 'en')).to be true
|
|
||||||
expect(service.supported?('uk', 'pt')).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not support non-included language as target language' do
|
|
||||||
expect(service.supported?('uk', 'nl')).to be false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not support non-included language as source language' do
|
|
||||||
expect(service.supported?('da', 'en')).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#translate' do
|
|
||||||
it 'returns translation with specified source language' do
|
|
||||||
stub_request(:post, 'https://api.deepl.com/v2/translate')
|
|
||||||
.with(body: 'text=Hasta+la+vista&source_lang=ES&target_lang=en&tag_handling=html')
|
|
||||||
.to_return(body: '{"translations":[{"detected_source_language":"ES","text":"See you soon"}]}')
|
|
||||||
|
|
||||||
translation = service.translate('Hasta la vista', 'es', 'en')
|
|
||||||
expect(translation.detected_source_language).to eq 'es'
|
|
||||||
expect(translation.provider).to eq 'DeepL.com'
|
|
||||||
expect(translation.text).to eq 'See you soon'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns translation with auto-detected source language' do
|
|
||||||
stub_request(:post, 'https://api.deepl.com/v2/translate')
|
|
||||||
.with(body: 'text=Guten+Tag&source_lang&target_lang=en&tag_handling=html')
|
|
||||||
.to_return(body: '{"translations":[{"detected_source_language":"DE","text":"Good Morning"}]}')
|
|
||||||
|
|
||||||
translation = service.translate('Guten Tag', nil, 'en')
|
|
||||||
expect(translation.detected_source_language).to eq 'de'
|
|
||||||
expect(translation.provider).to eq 'DeepL.com'
|
|
||||||
expect(translation.text).to eq 'Good Morning'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#languages?' do
|
|
||||||
it 'returns source languages' do
|
|
||||||
expect(service.send(:languages, 'source')).to eq ['en', 'uk', nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns target languages' do
|
|
||||||
expect(service.send(:languages, 'target')).to eq %w(en-gb zh en pt)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#request' do
|
|
||||||
before do
|
|
||||||
stub_request(:any, //)
|
|
||||||
# rubocop:disable Lint/EmptyBlock
|
|
||||||
service.send(:request, :get, '/v2/languages') { |res| }
|
|
||||||
# rubocop:enable Lint/EmptyBlock
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'uses paid plan base URL' do
|
|
||||||
expect(a_request(:get, 'https://api.deepl.com/v2/languages')).to have_been_made.once
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'with free plan' do
|
|
||||||
let(:plan) { 'free' }
|
|
||||||
|
|
||||||
it 'uses free plan base URL' do
|
|
||||||
expect(a_request(:get, 'https://api-free.deepl.com/v2/languages')).to have_been_made.once
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'sends API key' do
|
|
||||||
expect(a_request(:get, 'https://api.deepl.com/v2/languages').with(headers: { Authorization: 'DeepL-Auth-Key my-api-key' })).to have_been_made.once
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,71 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
RSpec.describe TranslationService::LibreTranslate do
|
|
||||||
subject(:service) { described_class.new('https://libretranslate.example.com', 'my-api-key') }
|
|
||||||
|
|
||||||
before do
|
|
||||||
stub_request(:get, 'https://libretranslate.example.com/languages').to_return(
|
|
||||||
body: '[{"code": "en","name": "English","targets": ["de","es"]},{"code": "da","name": "Danish","targets": ["en","de"]}]'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#supported?' do
|
|
||||||
it 'supports included language pair' do
|
|
||||||
expect(service.supported?('en', 'de')).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not support reversed language pair' do
|
|
||||||
expect(service.supported?('de', 'en')).to be false
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'supports auto-detecting source language' do
|
|
||||||
expect(service.supported?(nil, 'de')).to be true
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'does not support auto-detecting for unsupported target language' do
|
|
||||||
expect(service.supported?(nil, 'pt')).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#languages' do
|
|
||||||
subject(:languages) { service.send(:languages) }
|
|
||||||
|
|
||||||
it 'includes supported source languages' do
|
|
||||||
expect(languages.keys).to eq ['en', 'da', nil]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes supported target languages for source language' do
|
|
||||||
expect(languages['en']).to eq %w(de es)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'includes supported target languages for auto-detected language' do
|
|
||||||
expect(languages[nil]).to eq %w(de es en)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#translate' do
|
|
||||||
it 'returns translation with specified source language' do
|
|
||||||
stub_request(:post, 'https://libretranslate.example.com/translate')
|
|
||||||
.with(body: '{"q":"Hasta la vista","source":"es","target":"en","format":"html","api_key":"my-api-key"}')
|
|
||||||
.to_return(body: '{"translatedText": "See you"}')
|
|
||||||
|
|
||||||
translation = service.translate('Hasta la vista', 'es', 'en')
|
|
||||||
expect(translation.detected_source_language).to eq 'es'
|
|
||||||
expect(translation.provider).to eq 'LibreTranslate'
|
|
||||||
expect(translation.text).to eq 'See you'
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns translation with auto-detected source language' do
|
|
||||||
stub_request(:post, 'https://libretranslate.example.com/translate')
|
|
||||||
.with(body: '{"q":"Guten Morgen","source":"auto","target":"en","format":"html","api_key":"my-api-key"}')
|
|
||||||
.to_return(body: '{"detectedLanguage":{"confidence":92,"language":"de"},"translatedText":"Good morning"}')
|
|
||||||
|
|
||||||
translation = service.translate('Guten Morgen', nil, 'en')
|
|
||||||
expect(translation.detected_source_language).to be_nil
|
|
||||||
expect(translation.provider).to eq 'LibreTranslate'
|
|
||||||
expect(translation.text).to eq 'Good morning'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -114,85 +114,6 @@ RSpec.describe Status, type: :model do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#translatable?' do
|
|
||||||
before do
|
|
||||||
allow(TranslationService).to receive(:configured?).and_return(true)
|
|
||||||
allow(TranslationService).to receive(:configured).and_return(TranslationService.new)
|
|
||||||
allow(TranslationService.configured).to receive(:supported?).with('es', 'en').and_return(true)
|
|
||||||
|
|
||||||
subject.language = 'es'
|
|
||||||
subject.visibility = :public
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'all conditions are satisfied' do
|
|
||||||
it 'returns true' do
|
|
||||||
expect(subject.translatable?).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'translation service is not configured' do
|
|
||||||
it 'returns false' do
|
|
||||||
allow(TranslationService).to receive(:configured?).and_return(false)
|
|
||||||
allow(TranslationService).to receive(:configured).and_raise(TranslationService::NotConfiguredError)
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'status language is nil' do
|
|
||||||
it 'returns true' do
|
|
||||||
subject.language = nil
|
|
||||||
allow(TranslationService.configured).to receive(:supported?).with(nil, 'en').and_return(true)
|
|
||||||
expect(subject.translatable?).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'status language is same as default locale' do
|
|
||||||
it 'returns false' do
|
|
||||||
subject.language = I18n.locale
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'status language is unsupported' do
|
|
||||||
it 'returns false' do
|
|
||||||
subject.language = 'af'
|
|
||||||
allow(TranslationService.configured).to receive(:supported?).with('af', 'en').and_return(false)
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'default locale is unsupported' do
|
|
||||||
it 'returns false' do
|
|
||||||
allow(TranslationService.configured).to receive(:supported?).with('es', 'af').and_return(false)
|
|
||||||
I18n.with_locale('af') do
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'default locale has region' do
|
|
||||||
it 'returns true' do
|
|
||||||
I18n.with_locale('en-GB') do
|
|
||||||
expect(subject.translatable?).to be true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'status text is blank' do
|
|
||||||
it 'returns false' do
|
|
||||||
subject.text = ' '
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'status visiblity is hidden' do
|
|
||||||
it 'returns false' do
|
|
||||||
subject.visibility = 'limited'
|
|
||||||
expect(subject.translatable?).to be false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#content' do
|
describe '#content' do
|
||||||
it 'returns the text of the status if it is not a reblog' do
|
it 'returns the text of the status if it is not a reblog' do
|
||||||
expect(subject.content).to eql subject.text
|
expect(subject.content).to eql subject.text
|
||||||
|
|
|
@ -7,7 +7,6 @@ const express = require('express');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const redis = require('redis');
|
const redis = require('redis');
|
||||||
const pg = require('pg');
|
const pg = require('pg');
|
||||||
const dbUrlToConfig = require('pg-connection-string').parse;
|
|
||||||
const log = require('npmlog');
|
const log = require('npmlog');
|
||||||
const url = require('url');
|
const url = require('url');
|
||||||
const uuid = require('uuid');
|
const uuid = require('uuid');
|
||||||
|
@ -24,6 +23,43 @@ dotenv.config({
|
||||||
|
|
||||||
log.level = process.env.LOG_LEVEL || 'verbose';
|
log.level = process.env.LOG_LEVEL || 'verbose';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} dbUrl
|
||||||
|
* @return {Object.<string, any>}
|
||||||
|
*/
|
||||||
|
const dbUrlToConfig = (dbUrl) => {
|
||||||
|
if (!dbUrl) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = url.parse(dbUrl, true);
|
||||||
|
const config = {};
|
||||||
|
|
||||||
|
if (params.auth) {
|
||||||
|
[config.user, config.password] = params.auth.split(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.hostname) {
|
||||||
|
config.host = params.hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.port) {
|
||||||
|
config.port = params.port;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.pathname) {
|
||||||
|
config.database = params.pathname.split('/')[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
const ssl = params.query && params.query.ssl;
|
||||||
|
|
||||||
|
if (ssl && ssl === 'true' || ssl === '1') {
|
||||||
|
config.ssl = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object.<string, any>} defaultConfig
|
* @param {Object.<string, any>} defaultConfig
|
||||||
* @param {string} redisUrl
|
* @param {string} redisUrl
|
||||||
|
|
|
@ -8322,11 +8322,6 @@ pg-connection-string@^2.4.0:
|
||||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10"
|
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.4.0.tgz#c979922eb47832999a204da5dbe1ebf2341b6a10"
|
||||||
integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==
|
integrity sha512-3iBXuv7XKvxeMrIgym7njT+HlZkwZqqGX4Bu9cci8xHZNT+Um1gWKqCsAzcC0d95rcKMU5WBg6YRUcHyV0HZKQ==
|
||||||
|
|
||||||
pg-connection-string@^2.5.0:
|
|
||||||
version "2.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.5.0.tgz#538cadd0f7e603fc09a12590f3b8a452c2c0cf34"
|
|
||||||
integrity sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==
|
|
||||||
|
|
||||||
pg-int8@1.0.1:
|
pg-int8@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
|
||||||
|
|
Loading…
Reference in a new issue